'  ==========================================================================
'  |                                                                        |
'  | SQLitening                                                             |
'  |                                                                        |
'  ==========================================================================
'
'  Contains routines called by programs to access SQLite databases and flat
'  files residing locally and/or remotely.  The calling program must support
'  passing OLE strings both ByVal and ByRef. See SQLitening.Txt or SQLitening.Chm
'  for information on each command.

'  ==========================================================================
'  PUBLIC DOMAIN SOFTWARE
'  The author or authors of this code dedicate any and all
'  copyright interest in this code to the public domain.
'  Anyone is free to copy, modify, publish, use, compile,
'  sell, or distribute the original code, either in source
'  code form or as a compiled binary, for any purpose,
'  commercial or non-commercial, and by any means.
'  Fred Meier - July 2010
'  ==========================================================================

'  2011-05-21 DremLIN.Ru - Start modifications

#If Not %Def(%SLSQL)
   #Compile Dll "..\Bin\SQLitening.Dll"
   #Dim All
#EndIf

#If %Def(%SLSQL) And %Def(%SLSQL_ONLY)
   #Include "..\Src\SQLiteningClient.Bas"
#EndIf


'=======================<[ Equates and Types ]>========================
' ==========>>> SQLitening return codes
%SQLitening_NoOpenDatabase          =  -7
%SQLitening_AccessDenied            =  -8
%SQLitening_FileDoesNotExist        =  -9
%SQLitening_FileOpenGetPutError     = -10
%SQLitening_LockTimeout             = -11
%SQLitening_NochangedRows           = -12
%SQLitening_InvalidColumnNameNumber = -13
%SQLitening_InvalidSetNumber        = -14
%SQLitening_AttachIsInvalid         = -15
%SQLitening_CanNotConnect           = -16
%SQLitening_InvalidKeyOrNotFound    = -17
%SQLitening_SendOrReceiveError      = -18
%SQLitening_InvalidStringOrRequest  = -19
%SQLitening_ErrorAtServer           = -20
%SQLitening_MiscError               = -21

' ==========>>> SQLite return codes
%SQLITE3_OK               =   0     '*/ Successful result */
%SQLITE3_BUSY             =   5     '*/ The database file is locked */
%SQLITE3_ROW              = 100     '*/ sqlite3_step() has another row ready */
%SQLITE3_DONE             = 101     '*/ sqlite3_step() has finished executing */
%SQLITE_OPEN_READONLY     = &H00000001
%SQLITE_OPEN_READWRITE    = &H00000002
%SQLITE_OPEN_CREATE       = &H00000004

' ==========>>> Flags
Type GlobalFlags_TYPE
   ReturnAllErrors As Bit * 1 In Dword
   DisplayErrorMessage As Bit * 1
   DisplayErrorMessageQuestion As Bit * 1
End Type
Type ThreadedFlags_TYPE
   AreRunningRemote As Bit * 1 In Dword
   AreConnected As Bit * 1
   AllowAttachSQL As Bit * 1
End Type
Type DbFlags_TYPE
   DoNotRetryIfBusy As Bit * 1 In Dword
   DabInheritedSoCantClose As Bit * 1
End Type
Union DbFlags_UNION
   Bits As DbFlags_TYPE
   tuDbFlags As Dword
End Union

' ==========>>> Misc
%True = -1
%False = 0
$Title = "SQLitening"
%IDNO = 7
%MB_ICONQUESTION = &H00000020&
%MB_YESNO = &H00000004&
%MB_IconStop = &H00000010&

' ==========>>> Conditional Compile Flags
%ReturnAllErrors = %False

'=========================<[ SQLite Declares ]>=========================
' ==========>>> Using Ones
Declare Function UsingAttach(ByVal rhDab As Dword, rsFileName As String, rsAsName As String) As Long
Declare Function UsingBackupFinish CDecl (ByVal rhDabBu As Dword) As Long
Declare Function UsingBackupInit CDecl(ByVal rhSQLiteDest As Dword, rzDestName As Asciiz, ByVal hSQLiteSour As Dword, rzSourName As Asciiz) As Long
Declare Function UsingBackupStep CDecl(ByVal rhDabBu As Dword, ByVal rlPages As Long) As Long
Declare Function UsingBindTextOrBlob CDecl(ByVal rhSet As Dword, ByVal rlPos As Long, ByVal rhData As Dword, ByVal rlLen As Long, ByVal rhZero As Dword) As Long
Declare Function UsingBindInteger CDecl(ByVal rhSet As Dword, ByVal rlPos As Long, ByVal rlInteger As Long) As Long
Declare Function UsingBindQuad CDecl(ByVal rhSet As Dword, ByVal rlPos As Long, ByVal rqQuad As Quad) As Long
Declare Function UsingBindDouble CDecl(ByVal rhSet As Dword, ByVal rlPos As Long, ByVal rdDouble As Double) As Long
Declare Function UsingBindNull CDecl(ByVal rhSet As Dword, ByVal rlPos As Long) As Long
Declare Function UsingChanges CDecl(ByVal rhDab As Dword) As Long
Declare Function UsingClose CDecl(ByVal rhDab As Dword)As Long
Declare Function UsingCloseSet(ByVal rlSetNumber As Long)As Long
Declare Function UsingConnect(rsServer As String, ByVal rlPort As Long, rsModChars As String, wsOutData  As String)As Long
Declare Function UsingCopyDatabase(ByVal rhDab As Dword, ByVal rhDestDab As Dword, rsDatabaseName As String, ByVal rlPages As Long, ByVal rlSleep As Long) As Long
Declare Function UsingExe CDecl(ByVal rhDab As Dword, ByVal rspStatements As Dword, ByVal Zero As Dword, ByVal Zero As Dword, ByVal Zero As Dword) As Long
Declare Function UsingExeBindRemote(ByVal rhDab As Dword, rsStatement As String, rsBindData As String, ByVal rlFlags As Long, ByVal rlVarRowCount As Long) As Long
Declare Function UsingFinalizeLocal CDecl(ByVal rhSet As Dword)As Long
Declare Function UsingGetColAddress CDecl(ByVal rhSet As Dword, ByVal rlCol As Long)As Dword
Declare Function UsingGetColLength CDecl(ByVal rhSet As Dword, ByVal rlCol As Long)As Long
Declare Function UsingGetColName CDecl(ByVal rhSet As Dword, ByVal rlCol As Long)As Dword
Declare Function UsingGetColType(ByVal rhSetHandOrNum As Dword, ByVal rlCol As Long) As Long
Declare Function UsingGetErrorDescriptionLocal CDecl(ByVal rhDab As Dword) As Dword
Declare Function UsingGetErrorDescriptionRemote CDecl(ByVal rhDab As Dword) As String
Declare Function UsingGetFile(rsFileName As String, wsFileData As String, rsModChars As String)As Long
Declare Function UsingGetStatus(ByVal rhDab As Dword, ByVal rlRequest As Long, wsStatus As String) As Long
Declare Function UsingGetText(ByVal rlSetNumber As Long, ByVal rlCol As Long)As String
Declare Function UsingLastInsertRowid CDecl(ByVal rhDab As Dword) As Quad
Declare Function UsingNextRow(ByVal rhDab As Dword, ByVal rlSetNumber As Long)As Long
Declare Function UsingOpen CDecl(rzFilename As Asciiz, whSQLite As Dword, ByVal rlFlags As Long, ByVal rlNull As Long)As Long
Declare Function UsingPrepareLocal CDecl(ByVal rhDab As Dword, ByVal rspStatement As Dword, ByVal rlLen As Long, whSet As Dword, ByVal pzTail As Asciiz Ptr)As Long
Declare Function UsingPutFile(rsFileName As String, rsFileData As String, rsModChars As String)As Long
Declare Function UsingSel (ByVal rhDab As Dword, rsStatement As String, ByVal rlSetNumber As Long, rsModChars As String, wsColumnNames As String) As Long
Declare Sub      UsingSetBusyTimeout CDecl(ByVal rhDab As Dword, ByVal rlWaitMS As Long)
Declare Function UsingSetRelNamedLocks(ByVal rhDab As Dword, ByRef rsLockNames As String, ByVal rsModChars As String, ByVal rsSelStatement As String, ByVal rlSelSetNumber As Long, ByVal rsSelModChars As String, wsColumnNames As String) As Long
Declare Sub      UsingSleep CDecl(ByVal rlMilliSeconds As Long)
Declare Function UsingStepLocal CDecl(ByVal pStmt As Long)As Long
Declare Function UsingTotalChanges CDecl(ByVal rhDab As Dword) As Long
Declare Function UsingAuxRutsA(ByVal rlAction As Long, rsText As String, wsText As String) As Long
Declare Function UsingResetLocal CDecl(ByVal rhSet As Dword)As Long
Declare Function UsingRunProcLocal(ByVal rhDab As Dword, ByVal rlTcpFileNumber As Long, blParm1 As Long, blParm2 As Long, bsParm3 As String, bsParm4 As String) As Long
Declare Function UsingRunProcRemote(ByVal rhDab As Dword, rsProcName As String, blParm1 As Long, blParm2 As Long, bsParm3 As String, bsParm4 As String, rsModChars As String) As Long
Declare Function UsingEnableLoadExtension CDecl(ByVal rhDab As Dword, ByVal rlOnOrOff As Long) As Long

'=========================<[ Misc Declares ]>==========================
Declare Function GetPrivateProfileString Lib "KERNEL32.DLL" Alias "GetPrivateProfileStringA" (lpApplicationName As Asciiz, lpKeyName As Asciiz, lpDefault As Asciiz, lpReturnedString As Asciiz, ByVal nSize As Dword, lpFileName As Asciiz) As Dword
Declare Function GetModuleHandle Lib "KERNEL32.DLL" Alias "GetModuleHandleA" (lpModuleName As Asciiz) As Dword
Declare Function GetProcAddress Lib "KERNEL32.DLL" Alias "GetProcAddress" (ByVal hModule As Dword, lpProcName As Asciiz) As Long
Declare Sub      ExitProcess Lib "KERNEL32.DLL" Alias "ExitProcess" (ByVal uExitCode As Long)
Declare Function FreeLibrary Lib "KERNEL32.DLL" Alias "FreeLibrary" (ByVal hLibModule As Dword) As Long
Declare Function LoadLibrary Lib "KERNEL32.DLL" Alias "LoadLibraryA" (lpLibFileName As Asciz) As Long
Declare Function WritePrivateProfileString Lib "KERNEL32.DLL" Alias "WritePrivateProfileStringA" (lpApplicationName As Asciiz, lpKeyName As Asciiz, lpString As Asciiz, lpFileName As Asciiz) As Long

'============================<[ Globals ]>=============================
Global   guFlags As GlobalFlags_TYPE   'General purpose flags.
Threaded thDab As Dword                'Open and current database handle.
Threaded thaSets() As Dword            'Array of pointers to prepared sets.
Threaded twaColumnCounts() As Word     'Array of number of columns in prepared sets.
Threaded tsaNames() As String          'Array of lists of field names aligned with sets.
                                       'Stored twice delimited by $VT. The first
                                       'is all upper case delimited with NULs
                                       'and also with an extra NUL at beginning
                                       'and end for faster name to number conversion.
                                       'The second is not upper cased also delimited
                                       'by NULs but no extras are added.
Threaded tsaStack() As String          'Array of pushed database/set handles.
Threaded tlStackIX As Long             'Index for stack array.
Threaded thSQLiteLibHand As Long       'Handle of either SQLite3.DLL of SQLiteningCliend.DLL.
                                       '  Access Mode is always one of the following:
                                       '     Local = SQLite3.DLL
                                       '     Remote = SQLiteningClient.DLL
                                       '     Neutral = zero
Threaded tlLastError As Long           'Error return number from last routine.
Threaded tuFlags As ThreadedFlags_TYPE 'General purpose flags.
Threaded tuDbFlags As DbFlags_UNION    'Database flags

' ==========>>> Hi Use SQLite Call Proc Address
Threaded thCallGetColAddress As Dword
Threaded thCallGetColLength As Dword
Threaded thCallSel As Dword
Threaded thCallExe As Dword
Threaded thCallFinalize As Dword
Threaded thCallPrepare As Dword
Threaded thCallStep As Dword
Threaded thCallGetColumnName As Dword
Threaded thCallNextRow As Dword
Threaded thCallGetColumnText As Dword

'=======================<[ Internal Ruts ]>========================
' ==========>>> Get Rut Address
Sub irGetRutAddress (rsRutName As String, whAddress As Dword)
' Returns the address in Address.  If error will ExitProcess with a 9.

   ' If never loaded any lib then do it now
   If IsFalse thSQLiteLibHand Then Call irLoadProcessingLibAndRuts(%False)

   If thSQLiteLibHand = -1 Then
      whAddress = GetProcAddress(0, ByCopy rsRutName)
   Else
      whAddress = GetProcAddress(thSQLiteLibHand, ByCopy rsRutName)
   End If

   If whAddress = 0 Then
      MsgBox "Can not get routine """ & rsRutName & """", %MB_IconStop, $Title
      ExitProcess 9
   End If

End Sub

' ==========>>> Handle Error
Sub irHandleError(rsStatement As String, Optional ByVal rsModChars As String, Optional ByVal rlErrorNumber As Long)
'   If passed "E" ModChar or global flag ReturnAllErrors is on
'   and no "e"  ModChar then will just return else will display
'   "SQLite or SQLietning returned the following unexpected message!"
'   followed by the error message and then followed by the passed
'   Statement.   Will close any open database and then exit the
'   process.

   Local llA As Long
   Local llFlags As Long                        '&H01 - On = Do not exit process
                                                '&H02 - On = Do not show message
                                                '&H10 - On = Message is question
   Local lhRutAddr As Dword
   Local lsError As String
   Static sbWereEnding As Byte

   ' Due to events continuing while we are attempting to end then we
   ' must ignore any that come later
   If sbWereEnding Then Exit Sub

   ' Init flags -- if e passed then we use defaults which is all flags off
   If IsFalse InStr(rsModChars, "e") Then
      llA = InStr(rsModChars, "E")
      If llA Then
         llFlags = llFlags Or &H01     ' do not exit process
         llA = Val(Mid$(rsModChars, llA + 1))
         If llA = 0 Then
            llFlags = llFlags Or &H02     ' do not show message
         ElseIf llA = 2 Then
            llFlags = llFlags Or &H10     ' message is question
         End If
       Else
         If guFlags.ReturnAllErrors Then llFlags = llFlags Or &H03     ' do not exit process and do not show message
         If guFlags.DisplayErrorMessage Or guFlags.DisplayErrorMessageQuestion Then llFlags = llFlags And Not &H02     ' show message
         If guFlags.DisplayErrorMessageQuestion Then llFlags = llFlags Or &H10     ' message is question
      End If
   End If

   ' Set static we're ending if we actuall are
   If llFlags = 0 Then sbWereEnding = %True

   ' Set global last error if error number passed to us
   If rlErrorNumber Then tlLastError = rlErrorNumber

   ' Just exit sub if no message and no exit process
   If (llFlags And &H03) = &H03 Then Exit Sub

   ' Build message
   lsError = "SQLite or SQLitening returned the following unexpected message!"

   ' Add the error message if there is one
   lsError = lsError & String$(2, $Cr) & slGetError

   ' Add the statement
   lsError = lsError & String$(2, $Cr) & "Statement = " & rsStatement

   ' Show the error message.   &H00000021& = ok or cancel question, &H00000030& = warning, &H00000010& = severe
   For llA = 1 To 5 : Dialog DoEvents : Next    ' sometimes the message box will not show unless we dispatch messages
   If MsgBox(lsError, IIf&((llFlags And &H10), &H00000021&, IIf&((llFlags And &H01) = &H01, &H00000030&, &H00000010&)) + &H00002000&, $Title) = 2 Then llFlags = 0

   ' Just exit sub not exit process
   If (llFlags And &H01) Then Exit Sub

   'Close if needed
   If thDab And thSQLiteLibHand Then
      irGetRutAddress "sqlite3_close", lhRutAddr
      Call Dword lhRutAddr Using UsingClose(thDab)
   End If

   ' Exit process with exit code of 8
   ExitProcess 8

End Sub

' ==========>>> Load Library
Sub irLoadLib (rsLibName As String)
'   If OK will set thSQLiteLibHand.  If error will ExitProcess with a 9.

   thSQLiteLibHand = LoadLibrary(ByCopy rsLibName)
   If thSQLiteLibHand = 0 Then
      MsgBox "Can not load """ & rsLibName & """", %MB_IconStop, $Title
      ExitProcess 9
   End If

End Sub

' ==========>>> Is File There
Function irIsFileThere(rsFileName As String)As Long
' Returns %True if file exists

   Local llA As Long

   llA = GetAttr(rsFileName)
   If Err Then Exit Function
   Function = %True

End Function

' ==========>>> Is Date
Function irIsDate(rsDateTime As String)Export As Long
'   DateTime = Passed date and optional time.  Date must
'   be all numeric and be delimited by - or / (must be 2).
'   The optional time must be all numeric and be seperated
'   from date by one space.  Time can have any number of :
'   delimiters. The last value can be AM or PM(case insensitive)

   Local llA As Long
   Local lsA As String

   ' Get space count
   llA = Tally(rsDateTime, " ")

   ' If there are 3 parts delimite by one space then last must
   ' be AM or PM
   lsA = UCase$(Parse$(rsDateTime, " ", 3))
   If Len(lsA) Then
      If (lsA <> "AM" And lsA <> "PM") Or llA <> 2 Then Exit Function
   Else
      If llA <> 1 Then Exit Function
   End If

   ' Audit date
   lsA = Parse$(rsDateTime, " ", 1)
   If Tally(lsA, Any "-/") <> 2 Then Exit Function
   If Verify(Remove$(lsA, Any "-/"), "0123456789") Then Exit Function

   ' Audit time
   lsA = Parse$(rsDateTime, " ", 2)
   If Tally(lsA, Any "-/") > 4 Then Exit Function
   If Verify(Remove$(lsA, ":"), "0123456789") Then Exit Function

   ' Seems OK
   Function = %True

End Function

' ==========>>> Check If Busy
Function irCheckIfBusy(rsStatement As String) As Long
'   Returns %True if was busy and should try again

   If tlLastError <> %SQLITE3_BUSY Or IsTrue tuDbFlags.Bits.DoNotRetryIfBusy Then Exit Function
   If MsgBox(Left$(rsStatement, 255) & String$(2, $Cr) & "The above request was unsuccessful because the database is currently locked.  Want to try again?", %MB_ICONQUESTION + %MB_YESNO, $Title) = %IDNo Then Exit Function

   Function = %True

End Function

' ==========>>> Get Column Number
Function irGetColumnNumber(rsColumnName As String, _
                             ByVal rlSetNumber As Long) As Long
'   Will find the ColumnName.  Returns the column number if found
'   or zero if not found.  Assumes SetNumber is valid.

   Local llA As Long

   ' Look up position
   llA = InStr(tsaNames(rlSetNumber), $Nul & UCase$(rsColumnName) & $Nul)
   If llA Then Function = Tally(Left$(tsaNames(rlSetNumber), llA), $Nul)

End Function

' ==========>>> Load processing lib and ruts -- either for local or remote
Sub irLoadProcessingLibAndRuts(ByVal rlIsRemote As Long)

   Local lhRutAddr As Dword

   ' Load proper lib
   If (rlIsRemote) Then

      ' Load remote
      #If %Def(%SLSQL_ONLY)
         #If %Def(%DegugEXE)
            thSQLiteLibHand = -1
         #Else
            thSQLiteLibHand = GetModuleHandle("libslsql.dll")
'            MsgBox("NOT %DegugEXE - GetModuleHandle(""libslsql.dll"")" & Str$(thSQLiteLibHand))
         #EndIf
      #ElseIf %Def(%SLSQL)
         irLoadLib "slsqlite.dll"
      #Else
         irLoadLib "SQLiteningClient.Dll"
      #EndIf
      tuFlags.AreRunningRemote = %True

      ' Load hi use ruts that are just in remote
      irGetRutAddress "SQLiteSel", thCallSel
      irGetRutAddress "SQLiteNextRow", thCallNextRow
      irGetRutAddress "SQLiteGetColumnText", thCallGetColumnText

   Else

      ' Load local
      irLoadLib "SQLite3.Dll"
      tuFlags.AreRunningRemote = %False

      ' Initialize SQLite
      irGetRutAddress "sqlite3_initialize", lhRutAddr
      Call Dword lhRutAddr

      ' Load hi use ruts that are just in local
      irGetRutAddress "sqlite3_column_blob", thCallGetColAddress
      irGetRutAddress "sqlite3_column_bytes", thCallGetColLength
      irGetRutAddress "sqlite3_prepare_v2", thCallPrepare
      irGetRutAddress "sqlite3_step", thCallStep
      irGetRutAddress "sqlite3_finalize", thCallFinalize
      irGetRutAddress "sqlite3_column_name", thCallGetColumnName

   End If

   ' Load hi use ruts for both local and remote
   irGetRutAddress "sqlite3_exec", thCallExe

End Sub

' ==========>>> Check If Statement contains the Attach command or no open database
Function irCheckStatemetForAttachAndOpenDatabase(rsStatement As String) As Long
'   Will set tlLastError to return
'   Will return %SQLitening_NoOpenDatabase if no open database
'   Will return %SQLitening_AttachIsInvalid if statement contains the Attach command
'   and running remote.

   Local llA As Long

   ' Init stuff
   Reset tlLastError

   ' Check it
   If thDab Then
      If tuFlags.AllowAttachSQL Then Exit Function
      llA = InStr(UCase$(rsStatement), "ATTACH ")
      If tuFlags.AreRunningRemote And IsTrue llA And (IsFalse Len(RTrim$(Left$(rsStatement, llA - 1))) Or  Right$(RTrim$(Left$(rsStatement, llA - 1)), 1) = ";") Then tlLastError = %SQLitening_AttachIsInvalid
   Else
      tlLastError = %SQLitening_NoOpenDatabase
   End If

   ' Return
   Function = tlLastError

End Function

' ==========>>> Get Ini
Function irGetIni(rsFile As Asciiz, rsSection As Asciiz, rsEntity As Asciiz, wsData As String) As Long

   Local llLen As Long
   Local lzpData As Asciz Ptr

   ' Init stuff
   wsData = Space$(16384)  ' starting size

   ' Get it
   Do
   lzpData = StrPtr(wsData)
      llLen = GetPrivateProfileString(rsSection, rsEntity, $CrLf, @lzpData, Len(wsData), rsFile)
      If llLen < Len(wsData) - 2  Then
         ' If returning data is just the default $CRLF then it was not found
         If Left$(wsData, 2) = $CrLf Then
            ReturnNotFound:
            Reset wsData
            Function = %SQLitening_InvalidKeyOrNotFound
         Else
            wsData = RTrim$(Left$(wsData, llLen), $Nul)
         End If
         Exit Do
      End If
      wsData = Space$(Len(wsData) + 1638400)  ' next size
      ' If for some reason the data is larger than max then .....
      If IsFalse Len(wsData) Then GoTo ReturnNotFound
   Loop

End Function

' ==========>>> Call Aux Ruts A
Function irCallAuxRutsA (ByVal rlAction As Long, rsText As String, wsText As String) As Long
'   Action is:
'     1 = Set crypt key  Empty Text1 will
'         remove key, use for security reasons.
'     2 = Encrypt
'     3 = Decrypt
'     4 = Compress
'     5 = Uncompress
'     6 = Compress and Encrypt
'     7 = Convert to Hexadecimal
'     8 = Decrypt and Uncompress

   Local llRC As Long
   Local lhA As Dword
   Static shAddress As Dword

   If IsFalse shAddress Then
      lhA = thSQLiteLibHand      ' push SQL hand
      irLoadLib "SQLiteningAuxRuts.Dll"
      irGetRutAddress "AuxRutsA", shAddress
      thSQLiteLibHand = lhA       ' pop SQL hand
   End If
   Call Dword shAddress Using UsingAuxRutsA(rlAction, rsText, wsText) To llRC
   Function = llRC

End Function

' ==========>>> Get Field Value
Function irGetFieldValue(ByVal rlColumnNumber As Long, ByVal rlSetNumber As Long) As String
'   Returns the value for the field passed in ColumnNumber from the
'   current row of the set number passed in SetNumber.  Assumes
'   SetNumber is valid.  Will return empty if ColumnNumber invalid.

   Local llColLength As Long
   Local lhColAddress As Dword
   Local lsA As String

   ' Must handle differently if running remote
   If tuFlags.AreRunningRemote Then
      ' Running remote so get from buffer
      Call Dword thCallGetColumnText Using UsingGetText(rlSetNumber, rlColumnNumber - 1) To lsA
      Function = lsA
   Else
      ' Running local so get direct
      Call Dword thCallGetColLength Using UsingGetColLength(thaSets(rlSetNumber), rlColumnNumber - 1) To llColLength
      Call Dword thCallGetColAddress Using UsingGetColAddress(thaSets(rlSetNumber), rlColumnNumber - 1) To lhColAddress
      Function = Peek$(lhColAddress, llColLength)
   End If

End Function

' ==========>>> Pre Select Rut
Function irPreSelect(rsStatement As String, rlSetNumber As Long)As Long

   ' Init stuff
   If irCheckStatemetForAttachAndOpenDatabase(rsStatement) Then Exit Function

   ' Redim arrays if required
   If rlSetNumber > UBound(thaSets) Then
      ReDim Preserve thaSets(0 To rlSetNumber)
      ReDim Preserve twaColumnCounts(0 To rlSetNumber)
      ReDim Preserve tsaNames(0 To rlSetNumber)
   End If

   ' Audit set number -- it must be valid and closed
   If rlSetNumber < 0 Or rlSetNumber > 32767 Or thaSets(rlSetNumber) Then tlLastError = %SQLitening_InvalidSetNumber : Exit Function

   ' Exit OK
   Function = %True

End Function

' ==========>>> Post Select Rut
Function irPostSelect(rsColumnNames As String, rlSetNumber As Long, rsModChars As String)As Long

   Local llDo As Long
   Local lsUcaseColumnNames As String

   ' Build the ucase column names for faster lookup and check for duplicates
   lsUcaseColumnNames = $Nul & UCase$(rsColumnNames) & $Nul
   For llDo = 1 To ParseCount(rsColumnNames, $Nul)
      If Tally(lsUcaseColumnNames, $Nul & UCase$(Parse$(rsColumnNames, $Nul, llDo))) > 1 Then
         ' Might have duplicates so must check with multi instr's with trailing $NUL to be sure
         If InStr(lsUcaseColumnNames, $Nul & UCase$(Parse$(rsColumnNames, $Nul, llDo)) & $Nul) <> _
            InStr(-1, lsUcaseColumnNames, $Nul & UCase$(Parse$(rsColumnNames, $Nul, llDo)) & $Nul) And _
            IsFalse InStr(rsModChars, "D") Then
            slCloseSet rlSetNumber
            tlLastError = %SQLitening_InvalidColumnNameNumber
            Exit Function
         End If
      End If
   Next
   tsaNames(rlSetNumber) = lsUcaseColumnNames & $Vt & rsColumnNames

   ' Set the column count for this set number
   twaColumnCounts(rlSetNumber) =  ParseCount(Parse$(tsaNames(rlSetNumber), $Vt, 2), $Nul)

   ' Exit OK
   Function = %True

End Function

'=============================<[ Attach ]>=============================
Function slAttach Alias "slAttach" (rsFileName As String, _
                                    rsAsDatabaseName As String, _
                                    Optional ByVal rsModChars As String)Export As Long
'   Will attach the passed FileName using the AsDatabaseName. FileName
'   may also contain an optional password.  This password is separated
'   from the file name by the $BS character.
'   ModChars:
'      Em = Return errors. This will override the global return errors flag.
'           m is the optional message display modifier and can be:
'              0 = No message is displayed.  This is the default.
'              1 = Display a warning message with OK button.  Error is
'                  returned when OK pressed.
'              2 = Display a question message with OK and Cancel buttons.
'                  If they press OK, error is returned.  If they press
'                  Cancel, will exit process.
'      e  = Do not return errors, display message and exit process. This
'           will override the global return errors flag.
'   Returns zero if processed OK. Else, depending on ModChars and the global
'   return errors flag, will either display error and exit or will return
'   the error number.
'                --- Local Mode ---
'   If the FileName is not fully pathed then it is assumed to be relative to
'   the current folder which is the same folder, unless changed by chdir, that
'   the first .Exe was run from.  If fully pathed then no assumptions are
'   made.  Since this running on a local computer SQLitening allows the files
'   to be located anywhere on the local hard drives or local network drives.
'   Examples: If your .Exe started in C:\Apps\MyApp then:
'   If FileName is X\Y\Able.Sld then will assume file is in C:\Apps\MyApp\X\Y.
'   If FileName is ..\Y\Able.Sld then will assume file is in C:\Apps\Y.
'   If FileName is C:\Able.Sld then no assumption.
'                 --- Remote Mode ---
'   The FileName is assumed to be relative to the folder which the service is
'   running from.  Since this is running on a remote server SQLitening can not
'   allow the user to access files anyplace on the server.  Access is denied
'   to any FileName that has a colon, a double dot, or begins with a
'   backslash.  This will insure that the file is in same folder as the
'   service or below it.
'   Examples: If your service is running from C:\SQLitening then:
'   If FileName is Data\Able.Sld then will assume file is in C:\SQLitening\Data.
'   If FileName is ..\Y\Able.Sld then will get error %SQLitening_AccessDenied.
'   If FileName is C:\Able.Sld then will get error %SQLitening_AccessDenied.

   Local lhRutAddr As Dword
   Local lsFileName As String

   ' Init stuff
   lsFileName = Parse$(rsFileName, $Bs, 1)

   ' Must handle differently if running remote
   If tuFlags.AreRunningRemote Then

      ' Running remote
      irGetRutAddress "SQLiteAttach", lhRutAddr
      Call Dword lhRutAddr Using UsingAttach(thDab, rsFileName, rsAsDatabaseName) To tlLastError

   Else

      ' Running local

      ' If not in memory then file must be there
      If UCase$(lsFileName) <> ":MEMORY:" And Len(lsFileName) And IsFalse irIsFileThere(lsFileName) Then tlLastError = %SQLitening_FileDoesNotExist : GoTo ErrorRut

      ' Exec the attach
      tuFlags.AllowAttachSQL = %True
      tlLastError = slExe("Attach '" & lsFileName & "' as " & rsAsDatabaseName, "E")
      tuFlags.AllowAttachSQL = %False

   End If

   ' Check if OK
   If tlLastError = %SQLITE3_OK Then Exit Function
   ErrorRut:
   irHandleError "Attach """ & lsFileName & """", rsModChars
   Function = tlLastError

End Function

'==========================<[ BuildBindDat ]>==========================
Function slBuildBindDat Alias "slBuildBindDat" (rsData As String, _
                                                Optional ByVal rsModChars As String)Export As String
'   Returns a BindDat entry which is a specially formatted string required
'   by slExeBind.  Data contains the value you want converted into a BindDat.
'   A BindDat(s) is required to be passed to slExeBind.  The returned data
'   may also be compressed and/or encrypted.  If an error occurs then the
'   return value will be an empty string.  Use slGetError or slGetErrorNumber
'   to determine the error.
'   ModChars:
'      B = Bind as Blob.  This is the default.
'      C = Compress the data. Only use with Blob or Text.
'      N = Encrypt the data. Only use with Blob or Text. Requires a crypt key to
'          be set using slSetProcessMods.
'      T = Bind as Text.  Default is to bind as Blob.
'      i = Bind as Integer 32 bit (must be the only ModChar).  Default is to bind as Blob.
'      I = Bind as Integer 64 bit (must be the only ModChar).  Default is to bind as Blob.
'      D = Bind as Double (must be the only ModChar).  Default is to bind as Blob.
'      Z = Bind as Null (must be the only ModChar).  Default is to bind as Blob.
'      Note: If both C and N then will first compress and then encrypt.

   Local llA As Long
   Local lsA As String

   ' Init stuff
   Reset tlLastError

   ' Build request code
   llA = IIf&(InStr(rsModChars, "C"), 4, 0)
   llA = llA + IIf&(InStr(rsModChars, "N"), 2, 0)

   If llA Then

      ' Return compress and/or encrypt
      tlLastError = irCallAuxRutsA(llA, rsData, lsA)
      If tlLastError Then irHandleError "BuildBindDat"
      Function = MkDwd$(Len(lsA) + 1) & IIf$(InStr(rsModChars, "T"), "T", "B") & lsA

   Else

      ' Return as is
      Function = MkDwd$(Len(rsData) + 1) & IIf$(IsFalse Len(Trim$(rsModChars)), "B", Trim$(rsModChars)) & rsData
   End If

End Function

'======================<[ BuildInsertOrUpdate ]>=======================
Function slBuildInsertOrUpdate Alias "slBuildInsertOrUpdate" (rsTable As String, _
                                                              rsValues As String, _
                                                              Optional ByVal rsColumns As String, _
                                                              Optional ByVal rsWhere As String)Export As String
'   Returns either an Insert or Update SQL statement which is ready to pass to
'   slExe or slExeBind.  Many times you want to update a record if its there
'   or add it if it is not there.  The SQL syntax makes this clumsy, this
'   routine can help.  Values contains a delimited text string which is
'   $NUL separated.  $NUL was used because SQLite does not allow it to be in
'   any of the values.  Values are passed as alpha or non alpha (numeric,
'   NULL, or ?).  A value is consider numeric if it contains only
'   +-.0123456789 or ends with $VT.  If it does not end with $VT then there
'   can be only one + or - and it must be in the first position.  There can be
'   only one decimal point.  If it ends with $VT (used for passing expressions
'   like 126 * 85) then the $VT will be removed and the remaining will be
'   considered numeric.  Alpha values will have all embedded single quotes
'   changed to two single quotes and they will be enclosed within single
'   quotes.  If an alpha value is already enclosed in single quotes then it
'   will NOT be modified.  Examples:
'            -123.45     ----->    -123.45
'                        ----->       ''     (empty value passed)
'                ?       ----->        ?
'               '?'      ----->       '?'
'             +45-       ----->      '+45-'
'         126 * 85      ----->    126 * 85  (the  is $VT)
'            AB'CD       ----->    'AB''CD
'            Null        ----->     Null
'           'Null'       ----->    'Null'
'           'AJ'XY'      ----->    'AJ'XY'    (SQLite will raise error)
'   If Where is omitted or empty then will build an Insert statement else will
'   build an Update statement appending " Where " and the Where value.
'   Columns contains a comma delimited list of the column names.  Column names
'   are optional for Insert but required for Update.  This routine does not
'   call SQLite.  No error can occur.   Normal usage is as follows:
'      slExe slBuildInsertOrUpdate("TableA", "ABC" & $NUL & "123")
'      slExe slBuildInsertOrUpdate("TableA", "ABC" & $NUL & "123", "ColA,ColB", "rowid=1")

   Local llDo As Long
   Local llA As Long
   Local lsA As String

   ' Init stuff
   Reset tlLastError

   ' Create and load values array
   Dim lsaV(1 To ParseCount(rsValues, $Nul)) As String
   Parse rsValues, lsaV(), $Nul

   ' Fix up values.  If not numeric then change all
   ' singe quotes to doubles and inclose in single quotes.
   For llDo = 1 To UBound(lsav)

      ' Check if it needs quoting
      If IsFalse Len(lsaV(llDo)) Then GoTo QuoteIt    ' empty values must be quoted
      If Right$(lsaV(llDo), 1) = $Vt Then lsaV(llDo) = Left$(lsaV(llDo), -1) : Iterate For   ' it is numeric
      If Verify(lsaV(llDo), "+-.0123456789") = 0 Then ' it is a candidate for numeric -- must check more
         llA = Tally(lsaV(llDo), Any "+-")
         If (llA = 0 Or (llA = 1 And InStr(lsaV(llDo), Any "+-") = 1)) And Tally(lsaV(llDo), ".") <= 1 Then Iterate For ' it is numeric
      End If
      If lsaV(llDo) = "?" Or UCase$(lsaV(llDo)) = "NULL" Then Iterate For  ' ? and Null do not need quoting
      If Left$(lsaV(llDo), 1) = "'" And Right$(lsaV(llDo), 1) = "'" Then Iterate For   ' it is already quoted

      ' Replace embedded quote with doubles
      Replace "'" With "''" In lsaV(llDo)
      QuoteIt:
      lsaV(llDo) = "'" & lsaV(llDo) & "'"

   Next

   ' Build the returning statement
   If Len(rsWhere) Then
      For llDo = 1 To UBound(lsaV)
         lsA = lsA & IIf$(Len(lsA), ",", "") & Parse$(rsColumns, llDo) & "=" & lsaV(llDo)
      Next
      Function = "Update " & rsTable & " Set " & lsA & " Where " & rsWhere
   Else
      Function = "Insert into " & rsTable & IIf$(Len(rsColumns), "(" & rsColumns & ")", " ") & "Values(" & Join$(lsaV(), ",") & ")"
   End If

End Function

'=============================<[ Close ]>==============================
Sub slClose Alias "slClose" ()Export
'   Closes the current database handle and sets it to nothing.  Also will
'   close all sets.  This is optional.  All databases and sets will be
'   automatically closed when your program ends. If you are done with a
'   database but your program will continue to run for some time then it
'   is a good idea to close it.  Will not raise error if database is
'   already closed.  An inherited database handle can not be closed.
'   Normally no error will occur.  You can use slGetError or
'   slGetErrorNumber to be sure.

   Local llDo As Long
   Local lhRutAddr As Dword

   ' If can't close then just exit
   If tuDbFlags.Bits.DabInheritedSoCantClose Then Exit Sub

   ' Close all sets
   For llDo = 0 To UBound(thaSets)
      slCloseSet(llDo)
   Next

   ' Close database
   irGetRutAddress "sqlite3_close", lhRutAddr
   Call Dword lhRutAddr Using UsingClose(thDab) To tlLastError
   Reset thDab, tuDbFlags

End Sub

'===========================<[ Close Set ]>============================
Sub slCloseSet Alias "slCloseSet" (Optional ByVal rlSetNumber As Long)Export
'   Finalize the set handle passed in SetNumber and sets it to nothing.
'   A set is automatically closed when you NextRow through all the rows
'   or when your program ends. You need this function only when do not
'   NextRow through all the rows and you need the set closed because you
'   want to reuse the SetNumber.  Will not raise error if set is already
'   closed.

   Local lhRutAddr As Dword

   ' If set number is invalid or is not open then just exit
   If rlSetNumber < 0 Or rlSetNumber > UBound(thaSets) Or IsFalse thaSets(rlSetNumber) Then tlLastError = %SQLitening_InvalidSetNumber : Exit Sub

   'Must be handled differently if running remote
   If tuFlags.AreRunningRemote Then

      ' Running remote
      irGetRutAddress "SQLiteCloseSet", lhRutAddr
      Call Dword lhRutAddr Using UsingCloseSet(rlSetNumber) To tlLastError

   Else

      ' Running local
      Call Dword thCallFinalize Using UsingFinalizeLocal(thaSets(rlSetNumber)) To tlLastError

   End If

   ' Reset set arrays
   Reset thaSets(rlSetNumber), twaColumnCounts(rlSetNumber), tsaNames(rlSetNumber)

End Sub

'============================<[ Connect ]>=============================
Function slConnect Alias "slConnect" (Optional ByVal rsServer As String, _
                                      Optional ByVal rlPort As Long, _
                                      Optional ByVal rsModChars As String, _
                                      Optional wsOutData As String)Export As Long
'   Will connect to a SQLitening server.  This is only required if
'   you want to run in remote mode. Do not call this routine or set
'   Server to $NUL if you want to run in local mode. If Server is
'   omitted or empty it will default to LocalHost. Server can be
'   a host name, or an IP address (n.n.n.n)   If Port is zero or
'   omitted it will default to 51234.  If already connected then
'   will just return OK, no error is raised.
'   ModChars:
'      Em = Return errors. This will override the global return errors flag.
'           m is the optional message display modifier and can be:
'              0 = No message is displayed.  This is the default.
'              1 = Display a warning message with OK button.  Error is
'                  returned when OK pressed.
'              2 = Display a question message with OK and Cancel buttons.
'                  If they press OK, error is returned.  If they press
'                  Cancel, will exit process.
'      e  = Do not return errors, display message and exit process. This
'           will override the global return errors flag.
'      Tn = Where n is the how long a TCP SEND/ RECV should
'           wait for completion, in milliseconds (mSec). The
'           default timeout is 30000 milliseconds (30 seconds).
'           This wait should be set to be at least 3 times as
'           long as the wait for a database lock which that
'           default is 10000 milliseconds (10 seconds) and is
'           set in slOpen and can be changed in slSetProcessMods.
'      u  = Do not pass user name.
'      c  = Do not pass computer name.
'      i  = Do not pass IP address.
'   OutData, if parm is passed and if return is zero, will contain the
'   following values delimited by the $BS character.  There is only one
'   value being returned now but others may be added in future so use the
'   parse$ command.
'      1 = The unique TcpFileNumber assigned by the server to this connection.  This
'          can be used whenever a unique number for a connection is needed.  This
'          same value is passed to ceratin exits.
'   Returns zero if processed OK. Else, depending on ModChars and the global
'   return errors flag, will either display error and exit or will return
'   the error number.

   Local lhRutAddr As Dword
   Local lsA As String
   Local lsServer As String

   ' Init stuff
   Reset tlLastError, thDab
   lsServer = IIf$(Len(rsServer), rsServer, "LocalHost")

   ' Just eixt if......
   If rsServer = $Nul Then Exit Function        ' run local if passed as $NUL
   If tuFlags.AreConnected And slGetStatus(4) = "Yes" Then Exit Function   ' already connected

   ' Load remote libs if needed
   If IsFalse tuFlags.AreRunningRemote Then irLoadProcessingLibAndRuts %True

   ' Connect to server
   irGetRutAddress "SQLiteConnect", lhRutAddr
   Call Dword lhRutAddr Using UsingConnect(lsServer, rlPort, rsModChars, lsA) To tlLastError
   If tlLastError > 0 Then tlLastError = %SQLitening_CanNotConnect   ' TCP open error so change to %SQLitening_CanNotConnect.
   If tlLastError Then

      ' Handle error
      irHandleError "Connect -- Server=" & lsServer & " Port=" & Format$(rlPort), rsModChars  ' Handle error
   Else

      ' Set that we are connected
      tuFlags.AreConnected = %True
      ' Set returning data
      If IsFalse IsMissing(wsOutData) Then wsOutData = lsA
   End If
   Function = tlLastError

End Function

'===========================<[ ConvertDat ]>===========================
Function slConvertDat Alias "slConvertDat" (rsData As String, _
                                            Optional ByVal rsModChars As String)Export As String
'   Returns a string which has been converted per ModChars.  Data contains
'   the value you want converted. If an error occurs then the return value
'   will be an empty string.  Use slGetError or slGetErrorNumber to determine
'   the error.
'   ModChars:
'      C = Compress
'      N = Encrypt - Requires a crypt key to be set using slSetProcessMods.
'      D = Decrypt - Requires a crypt key to be set using slSetProcessMods.
'      U = Uncompress
'      X = Hexadecimal
'      Note: If C or N then D and U are ignored.
'            If both C and N then will first compress and then encrypt.
'            If both D and U then will first decrypt and then uncompress.
'      CAUTION: Requesting to uncompress a field that is not compressed
'               will cause unpredicable results!

   Local llA As Long
   Local lsA, lsB As String

   ' Init stuff
   Reset tlLastError

   ' Build request code
   llA = IIf&(InStr(rsModChars, "C"), 4, 0)
   llA = llA + IIf&(InStr(rsModChars, "N"), 2, 0)
   If IsFalse llA Then
      llA = IIf&(InStr(rsModChars, "D"), 3, 0)
      llA = llA + IIf&(InStr(rsModChars, "U"), 5, 0)
   End If
   If llA Then
      ' Return compress, uncompress, encrypt, or decrypt
      tlLastError = irCallAuxRutsA(llA, rsData, lsA)
      If tlLastError Then irHandleError "ConvertDat"
   Else
      lsA = rsData
   End If

   ' Hexadecimal if requested
   If InStr(rsModChars, "X") Then
      lsB = lsA
      tlLastError = irCallAuxRutsA(7, lsB, lsA)
   End If

   ' Return it
   Function = lsA

End Function

'=========================<[ Copy Database ]>==========================
Function slCopyDatabase Alias "slCopyDatabase" (rsDestinationFileName As String, _
                                                Optional ByVal rsModChars As String, _
                                                Optional ByVal rsDatabaseName As String)Export As Long
'   This will copy/backup the current database (FROM) file.  It will
'   overwrite or create a new database (TO) file. DestinationFileName is
'   the copied SQLite file (TO). It must not exist or be a valid SQLite
'   database file. There are two special destination file names:
'     1. ":memory:" = creates the copy (TO) as a temporary database in memory.
'     2. "" (empty) = creates the copy (TO) as a temporary database on disk.
'   DestinationFileName may also contain an optional password.  This
'   password is separated from the file name by the $BS character.
'   The optional DatabaseName is required when you want to copy a database
'   other than Main. This is needed when you are coping an attached
'   database. If it's an attached database then it is the same name that
'   was used in AsDatabaseName parm in the slAttach command. If you wanted
'   to copy your tempory tables then this name would be Temp.
'   ModChars:
'      A  = Activate (make current) the new (TO) database and close the
'           old one (FROM - was current).
'      a  = Activate (make current) the new (TO) database but push the
'           old one (FROM - was current) onto the stack (see slPushDatabase)
'           rather than closing it.
'     Note: If no A nor a then the new (TO) database is closed and the
'           old (FROM) remains current.
'      E  = Return errors.
'      P  = Number of database pages to copy before sleeping.
'           Default is 100.  Pass -1 to copy all pages without sleeping.
'      S  = Number of milliseconds to sleep before copying more pages.
'           Default is 25. This is ignored if P is -1.
'     Note: During sleep other processes can update the old (FROM) database.
'           The fewer pages you copy at a time results in more sleep periods
'           and the larger sleep time results in more time for other process
'           to update, but the time to copy will be longer.
'   Returns zero if processed OK. Else, depending on ModChars will either
'   display error and exit or will return the error number.
'                --- Local Mode ---
'   If the DestinationFileName is not fully pathed then it is assumed to be
'   relative to the current folder which is the same folder, unless changed
'   by chdir, that the first .Exe was run from.  If fully pathed then no
'   assumptions are made.  Since this running on a local computer SQLitening
'   allows the files to be located anywhere on the local hard drives or local
'   network drives.
'   Examples: If your .Exe started in C:\Apps\MyApp then:
'   If DestinationFileName is X\Y\Able.Sld then will assume file is in C:\Apps\MyApp\X\Y.
'   If DestinationFileName is ..\Y\Able.Sld then will assume file is in C:\Apps\Y.
'   If DestinationFileName is C:\Able.Sld then no assumption.
'                 --- Remote Mode ---
'   The DestinationFileName is assumed to be relative to the folder which the
'   service is running from.  Since this is running on a remote server SQLitening
'   can not allow the user to access files anyplace on the server.  Access is
'   denied to any DestinationFileName that has a colon, a double dot, or begins
'   with a backslash.  This will insure that the file is in same folder as the
'   service or below it. Access is denied to files that are in the Temp folder
'   (contains the Row Data Chunk overflows), any file whose extension is
'   .Exe, .Dll, .Log or .Cfg that reside in same folder as the service.  Also
'   for security reasons, all files must be included in the FACT  of the server
'   config file. This is because of the damage that can be done due to a coding
'   error with CopyDatabase.
'   Examples: If your service is running from C:\SQLitening then:
'   If DestinationFileName is Data\Able.Sld then will assume file is in C:\SQLitening\Data.
'   If DestinationFileName is ..\Y\Able.Sld then will get error %SQLitening_AccessDenied.
'   If DestinationFileName is C:\Able.Sld then will get error %SQLitening_AccessDenied.
'   If DestinationFileName is X\Y\Able.Exe and command was GetFile then will get error %SQLitening_AccessDenied.

   Local llRC As Long
   Local llPages, llSleep As Long
   Local lhRutAddr, lhRutAddr2 As Dword
   Local lhDestDab, lhHandBU As Dword

   ' Init stuff
   If irCheckStatemetForAttachAndOpenDatabase("") Then ErrorRut
   llRC = InStr(rsModChars, "P")
   llPages = IIf&(llRC, Val(Mid$(rsModChars, llRC + 1)), 100)
   llRC = InStr(rsModChars, "S")
   llSleep = IIf&(llRC, Val(Mid$(rsModChars, llRC + 1)), 25)
   If IsFalse(Len(rsDatabaseName)) Then rsDatabaseName = "Main"
   irGetRutAddress "sqlite3_open_v2", lhRutAddr

   ' Must handle differently if running remote
   If tuFlags.AreRunningRemote Then

      ' Running remote so.......
      ' Open destination
      Call Dword lhRutAddr Using UsingOpen(ByCopy rsDestinationFileName, _
                                           lhDestDab, _
                                           %SQLITE_OPEN_READWRITE Or %SQLITE_OPEN_CREATE, _
                                           0) To llRC
      If llRC <> %SQLITE3_OK Then GoTo ErrorRut

      irGetRutAddress "SQLiteCopyDatabase", lhRutAddr
      Call Dword lhRutAddr Using UsingCopyDatabase(thDab, lhDestDab, rsDatabaseName, llPages, llSleep) To llRC
      If llRC Then ErrorRut

   Else

      ' Running local so.......
      ' Open destination
      Call Dword lhRutAddr Using UsingOpen(Parse$(rsDestinationFileName, $Bs, 1), _
                                           lhDestDab, _
                                           %SQLITE_OPEN_READWRITE Or %SQLITE_OPEN_CREATE, _
                                           0) To llRC
      If llRC <> %SQLITE3_OK Then GoTo ErrorRut

      ' Init backup
      irGetRutAddress "sqlite3_backup_init", lhRutAddr
      Call Dword lhRutAddr Using UsingBackupInit(lhDestDab, _
                                                 "Main", _
                                                  thDab, _
                                                  ByCopy rsDatabaseName) To lhHandBU
      If IsFalse lhHandBU Then llRC = %SQLitening_MiscError : GoTo ErrorRut

      ' Step thru backup
      irGetRutAddress "sqlite3_backup_step", lhRutAddr
      irGetRutAddress "sqlite3_sleep", lhRutAddr2
      Do
         Call Dword lhRutAddr Using UsingBackupStep(lhHandBu, llPages) To llRC
         If llRC = %SQLITE3_OK Then
            Call Dword lhRutAddr2 Using UsingSleep(llSleep)
         ElseIf llRC <> %SQLITE3_DONE Then
            llRC = %SQLitening_MiscError
            GoTo ErrorRut
         Else
            Exit Do
         End If
      Loop

      ' Finish backup
      irGetRutAddress "sqlite3_backup_finish", lhRutAddr
      Call Dword lhRutAddr Using UsingBackupFinish(lhHandBu) To llRC
      If llRC <> %SQLITE3_OK Then GoTo ErrorRut

   End If

   ' Activate the new database if requested else we will close it
   If InStr(rsModChars, Any "aA") Then
      If InStr(rsModChars, "A") Then slClose Else slPushDatabase
      thDab = lhDestDab
   Else
      ' Close the copied database
      irGetRutAddress "sqlite3_close", lhRutAddr
      Call Dword lhRutAddr Using UsingClose(lhDestDab)
   End If

   ' Exit OK
   Exit Function

   ErrorRut:
      irGetRutAddress "sqlite3_close", lhRutAddr
      Call Dword lhRutAddr Using UsingClose(lhDestDab)
   irHandleError "CopyDatabase """ & rsDestinationFileName & """" & IIf$(Len(rsDatabaseName), "  """ & rsDatabaseName & """", ""), rsModChars, llRC
   Function = tlLastError

End Function

'===========================<[ Disconnect ]>===========================
Sub slDisconnect Alias "slDisconnect" Export
'   Disconnects from the server.  This is optional.  A connection will
'   be automatically disconnected when your program ends. If you are done
'   with a connection but your program will continue to run for some time
'   then it is a good idea to disconnect.  Also needed if running remote
'   and you now want to start running only local. No error will occur.

   Local lhRutAddr As Dword

   ' Init stuff
   Reset tlLastError

   ' Only use if we are connected.
   If tuFlags.AreConnected Then

      ' Disconnect from server
      irGetRutAddress "SQLiteDisconnect", lhRutAddr
      Call Dword lhRutAddr

      ' Clear running remote, set access mode to neutral,
      ' clear that we are connected, and clear database handle
      Reset tuFlags.AreRunningRemote, thSQLiteLibHand, tuFlags.AreConnected, thDab

   End If

End Sub

'==============================<[ Exe ]>===============================
Function slExe Alias "slExe" (rsStatement As String, _
                              Optional ByVal rsModChars As String)Export As Long
'   Executes one or more statements.  Each statement is separated by a
'   semicolon.
'   ModChars:
'      C  = Must change 1 or more rows. This will only work for the last statement.
'      Em = Return errors. This will override the global return errors flag.
'           m is the optional message display modifier and can be:
'              0 = No message is displayed.  This is the default.
'              1 = Display a warning message with OK button.  Error is
'                  returned when OK pressed.
'              2 = Display a question message with OK and Cancel buttons.
'                  If they press OK, error is returned.  If they press
'                  Cancel, will exit process.
'      e  = Do not return errors, display message and exit process. This
'           will override the global return errors flag.
'      R =  Release all named locks owned by this connection after doing
'           the Exe.
'   CAUTION: If ANY of the statements returns Busy then will restart with
'            the first statement.  Use Begin Immediate to prevent this
'            or set ProcessMods to %gbfDoNotRetryIfBusy.
'   Returns zero if processed OK. Else, depending on ModChars and the global
'   return errors flag, will either display error and exit or will return
'   the error number.

   ' Init stuff
   If irCheckStatemetForAttachAndOpenDatabase(rsStatement) Then GoTo ErrorRut

   ' Exe statements
   Do
      Call Dword thCallExe Using UsingExe(thDab, StrPtr(rsStatement), 0, IIf&(InStr(rsModChars, "R"), 1, 0), 0) To tlLastError
      If tlLastError And IsFalse irCheckIfBusy("Exe " & rsStatement) Then GoTo ErrorRut
   Loop While tlLastError = %SQLITE3_BUSY

   ' Check for required changes
   If InStr(rsModChars, "C") And IsFalse slGetChangeCount Then tlLastError = %SQLitening_NochangedRows : GoTo ErrorRut

   ' Exit OK
   Exit Function

   ErrorRut:
   irHandleError "Exe " & rsStatement, rsModChars
   Function = tlLastError

End Function

'============================<[ Exe Bind ]>============================
Function slExeBind Alias "slExeBind" (rsStatement As String, _
                                      rsBindDats As String, _
                                      Optional ByVal rsModChars As String)Export As Long
'   Statement contains one (no multiples like slExe) Insert or Update
'   statement.  Will replace all of the '?' expressions in Statement with the
'   corresponding BindDat entries passed in BindDats.  A BindDat entry is the
'   specially formated string returned by the slBuildBindDat function.  Pass
'   BindDats as multiply concatenated BindDat entries to handle multiple '?'
'   expressions.  The first '?' will be replaced with the first BindDat entry,
'   the second '?' with the second BindDat entry, etc.  Excess '?' expressions
'   will be set to Null while excess BindDat entries will raise an error.
'   You may use slExeBind to affect one or multiple records.  The default is to
'   affect only one record.  Add the V ModChar (see below) to affect multiple
'   records.  Using the V ModChar is a highly effecent/fast way to insert or
'   update many records.  Using slExeBind this way tells SQLite3 to only
'   prepare/compile the SQL statement one time and then affect each row re-using
'   the prepared/compiled statement. See the example below and also look at
'   ExampleB.Bas.
'   The following example will insert four columns into a single record:
'     The column types are --
'     1 = Blob, 2 = Compressed Text, 3 = Compressed Encrypted Blob, 4 = Double .
'        slExeBind "Insert into T1 values(?, ?, ?, ?)", _
'                                         slBuildBindDat("This is some Blob data") & _
'                                         slBuildBindDat("This is some compressed Text", "TC") & _
'                                         slBuildBindDat("This is a compressed and encrypted Blob", "CE") & _
'                                         slBuildBindDat("123.456", "D")
'   The following example will insert three columns into 50 records:
'     The column types are --
'     1 = Ingeger 32 bit, 2 = Null, 3 = Integer 64 bit
'        dim lsaRows(1 to 100) as String
'        for llDo = 1 to ubound(lsaRows)
'           lsaRows(llDo) = iif$(llDo mod 2, slBuildBindDat(format$(llDo), "i"), slBuildBindDat(format$(llDo + 1000^4), "I"))
'        next
'        slExeBind "Insert into T1 values(?, Null, ?)", join$(lsaRows(), ""), "V2"
'     Note the use of an array and the join$ command which is much faster then
'     standard concatenation.
'   ModChars:
'      Em = Return errors. This will override the global return errors flag.
'           m is the optional message display modifier and can be:
'              0 = No message is displayed.  This is the default.
'              1 = Display a warning message with OK button.  Error is
'                  returned when OK pressed.
'              2 = Display a question message with OK and Cancel buttons.
'                  If they press OK, error is returned.  If they press
'                  Cancel, will exit process.
'      e  = Do not return errors, display message and exit process. This
'           will override the global return errors flag.
'      R =  Release all named locks owned by this connection after doing
'           the ExeBind.
'      Vn = Optional.  Required if you want to Insert/Update more than one record.
'           Using this to affect many records will greatly improve the speed of
'           Inserts/Updates.  n is the number of "?"s in the Insert/Update statement.
'           If omitted or n is zero then only one record will be affected.  If n is
'           >zero then it must equal the number of "?"s.  See example above.
'   Returns zero if processed OK. Else, depending on ModChars and the global
'   return errors flag, will either display error and exit or will return
'   the error number.

   Local llBindIX As Long
   Local llVarRowCount As Long
   Local llDataOffset As Long
   Local lhHand As Dword
   Local lhRutAddrReset, lhRutAddrBindBlob, lhRutAddrBindText, lhRutAddrBindInt, lhRutAddrBindInt64, lhRutAddrBindDouble, lhRutAddrBindNull As Dword
   Local lhRutAddr As Dword

   ' Init stuff
   If irCheckStatemetForAttachAndOpenDatabase(rsStatement) GoTo ErrorRut
   llVarRowCount = InStr(rsModChars, "V")
   If llVarRowCount Then llVarRowCount = Val(Mid$(rsModChars, llVarRowCount + 1))

   ' Audit the bind data
   Do While llDataOffset < Len(rsBindDats)
      If IsFalse InStr(Mid$(rsBindDats, llDataOffset + 5, 1), Any "BTiIDZ") Then tlLastError = %SQLitening_InvalidStringOrRequest : GoTo ErrorRut
      llDataOffset = llDataOffset + CvDwd(rsBindDats, llDataOffset + 1) + 4
   Loop
   If IsFalse llDataOffset Or llDataOffset <> Len(rsBindDats) Then tlLastError = %SQLitening_InvalidStringOrRequest : GoTo ErrorRut
   Reset llDataOffset

   ' Bind it must be handled differently if running remote
   If tuFlags.AreRunningRemote Then

      ' Running remote so must do prep, binds and step at server
      irGetRutAddress "SQLiteExeBind", lhRutAddr
      Do
         Call Dword lhRutAddr Using UsingExeBindRemote(thDab, rsStatement, rsBindDats, IIf&(InStr(rsModChars, "R"), 1, 0), llVarRowCount) To tlLastError
         If tlLastError And  IsFalse irCheckIfBusy("ExeBind " & rsStatement) Then GoTo ErrorRut
      Loop While tlLastError = %SQLITE3_BUSY

   Else

      ' Running local so load rut addresses and prep statement
      irGetRutAddress "sqlite3_reset", lhRutAddrReset
      irGetRutAddress "sqlite3_bind_blob", lhRutAddrBindBlob
      irGetRutAddress "sqlite3_bind_text", lhRutAddrBindText
      irGetRutAddress "sqlite3_bind_int", lhRutAddrBindInt
      irGetRutAddress "sqlite3_bind_int64", lhRutAddrBindInt64
      irGetRutAddress "sqlite3_bind_double", lhRutAddrBindDouble
      irGetRutAddress "sqlite3_bind_null", lhRutAddrBindNull
      Do
         Call Dword thCallPrepare Using UsingPrepareLocal(thDab, StrPtr(rsStatement), Len(rsStatement) + 1, lhHand, 0) To tlLastError
         If tlLastError And IsFalse irCheckIfBusy("ExeBind " & rsStatement) Then GoTo ErrorRut
      Loop While tlLastError = %SQLITE3_BUSY

      ' Process each bind
      Do While llDataOffset < Len(rsBindDats)

         ' Incr bind index and check if ready to step
         Incr llBindIX
         If llVarRowCount And  llBindIX > llVarRowCount Then
            GoSub StepIt
            Call Dword lhRutAddrReset Using UsingResetLocal(lhHand)
            llBindIX = 1
         End If

         ' Do the bind based on the type
         Select Case Const$ Mid$(rsBindDats, llDataOffset + 5, 1)
         Case "B"
            Call Dword lhRutAddrBindBlob Using UsingBindTextOrBlob(lhHand, llBindIX, StrPtr(rsBindDats) + llDataOffset + 5, CvDwd(rsBindDats, llDataOffset + 1) - 1, 0) To tlLastError
         Case "T"
            Call Dword lhRutAddrBindText Using UsingBindTextOrBlob(lhHand, llBindIX, StrPtr(rsBindDats) + llDataOffset + 5, CvDwd(rsBindDats, llDataOffset + 1) - 1, 0) To tlLastError
         Case "i"
            Call Dword lhRutAddrBindInt Using UsingBindInteger(lhHand, llBindIX, Val(Mid$(rsBindDats, llDataOffset + 6))) To tlLastError
         Case "I"
            Call Dword lhRutAddrBindInt64 Using UsingBindQuad(lhHand, llBindIX, Val(Mid$(rsBindDats, llDataOffset + 6))) To tlLastError
         Case "D"
            Call Dword lhRutAddrBindDouble Using UsingBindDouble(lhHand, llBindIX, Val(Mid$(rsBindDats, llDataOffset + 6))) To tlLastError
         Case "Z"
            Call Dword lhRutAddrBindNull Using UsingBindNull(lhHand, llBindIX) To tlLastError
         End Select
         If tlLastError <> %SQLITE3_OK Then GoTo ErrorRut
         llDataOffset = llDataOffset + CvDwd(rsBindDats, llDataOffset + 1) + 4

      Loop

      ' Do last or only step
      GoSub StepIt

      ' Finalize it
      Call Dword thCallFinalize Using UsingFinalizeLocal(lhHand)

   End If

   ' Exit OK
   Exit Function

   StepIt:
   Do
      Call Dword thCallStep Using UsingStepLocal(lhHand) To tlLastError
      If tlLastError = 101 Then tlLastError = 0    '101 is not an error it means= sqlite_step() has finished executing
      If tlLastError And IsFalse irCheckIfBusy("ExeBind " & rsStatement) Then GoTo ErrorRut
   Loop While tlLastError = %SQLITE3_BUSY
   Return

   ErrorRut:
   irHandleError "ExeBind " & rsStatement, rsModChars
   Function = tlLastError
   ' If running local must finalize
   If IsFalse tuFlags.AreRunningRemote Then Call Dword thCallFinalize Using UsingFinalizeLocal(lhHand)

End Function

'===========================<[ Field Get ]>============================
Function slF Alias "slF" (ByVal rlColumnNumber As Long, _
                          Optional ByVal rlSetNumber As Long)Export As String
'   Returns the value for the field passed in ColumnNumber from the
'   current row for the set number passed in SetNumber.  Due to the
'   frequent usage of this routine it has a very short non-descript name.
'   If an invalid ColumnNumber or SetNumber is passed then will exit
'   process.

   ' Init stuff
   Reset tlLastError

   ' Audit set number and column number
   If rlSetNumber < 0 Or rlSetNumber > UBound(thaSets) Or IsFalse thaSets(rlSetNumber) Then irHandleError "GetField", "e", %SQLitening_InvalidSetNumber
   If rlColumnNumber < 1 Or rlColumnNumber > twaColumnCounts(rlSetNumber) Then irHandleError "GetField", "e", %SQLitening_InvalidColumnNameNumber

   ' Get field value
   Function = irGetFieldValue(rlColumnNumber, rlSetNumber)

End Function

'=========================<[ Field Name Get ]>=========================
Function slFN Alias "slFN" (rsColumnName As String, _
                            Optional ByVal rlSetNumber As Long)Export As String
'   Returns the value for the field passed in ColumnName from the current
'   row for the set number passed in SetNumber.  Due to the frequent usage
'   of this routine it has a very short non-descript name.  If an invalid
'   ColumnName or SetNumber is passed then will exit process.

   Local llA As Long

   ' Init stuff
   Reset tlLastError

   ' Audit set number
   If rlSetNumber < 0 Or rlSetNumber > UBound(thaSets) Or IsFalse thaSets(rlSetNumber) Then irHandleError "GetFieldName " & rsColumnName, "e", %SQLitening_InvalidSetNumber : Exit Function

   ' Get column number
   llA = irGetColumnNumber(rsColumnName, rlSetNumber)
   If IsFalse llA Then irHandleError "GetFieldName " & rsColumnName, "e", %SQLitening_InvalidColumnNameNumber : Exit Function

   ' Get field value
   Function = irGetFieldValue(llA, rlSetNumber)

End Function

'=======================<[ Field Get Extended ]>=======================
Function slFX Alias "slFX" (ByVal rlColumnNumber As Long, _
                            Optional ByVal rsModChars As String, _
                            Optional ByVal rlSetNumber As Long)Export As String
'   Returns the value for the field passed in ColumnNumber from the
'   current row for the set number passed in SetNumber.  Due to the
'   frequent usage of this routine it has a very short non-descript name.
'   ModChars:
'      Em = Return errors. This will override the global return errors flag.
'           m is the optional message display modifier and can be:
'              0 = No message is displayed.  This is the default.
'              1 = Display a warning message with OK button.  Error is
'                  returned when OK pressed.
'              2 = Display a question message with OK and Cancel buttons.
'                  If they press OK, error is returned.  If they press
'                  Cancel, will exit process.
'           If an error occurs then the return value will be an empty string.
'      e  = Do not return errors, display message and exit process. This
'           will override the global return errors flag.
'      N  = Return NULL fields as $NUL.  CAUTION: You can not distinguish
'           between a true NULL field and one that contains a single
'           $NUL char.
'      D  = Decrypt.
'      U  = Uncompress.
'      t  = If field is DateTime then do not return time.
'      z  = If field is DateTime then do not return time if zero.
'      d  = If field is DateTime then do not return date.
'      y  = If field is DateTime then return empty if time is zero.
'      Note: If both D and U then will first decrypt and then uncompress.
'      CAUTION: Requesting to uncompress a field that is not compressed
'               will cause unpredicable results!

   Local llA As Long
   Local lsA As String

   ' Init stuff
   Reset tlLastError

   ' Audit set number and column number
   If rlSetNumber < 0 Or rlSetNumber > UBound(thaSets) Or IsFalse thaSets(rlSetNumber) Then tlLastError = %SQLitening_InvalidSetNumber : GoTo ErrorRut
   If rlColumnNumber < 1 Or rlColumnNumber > twaColumnCounts(rlSetNumber) Then tlLastError = %SQLitening_InvalidColumnNameNumber : GoTo ErrorRut

   ' Check if they want to return null
   If InStr(rsModChars, "N") And IsTrue slIsFieldNull(rlColumnNumber, rlSetNumber) Then

      ' Return null
      Function = $Nul

   Else
      ' Process decrypt and uncompress
      llA = IIf&(InStr(rsModChars, "D"), 3, 0)
      llA = llA + IIf&(InStr(rsModChars, "U"), 5, 0)
      If llA Then
         tlLastError = irCallAuxRutsA(llA, irGetFieldValue(rlColumnNumber, rlSetNumber), lsA)
      Else
         lsA = irGetFieldValue(rlColumnNumber, rlSetNumber)
      End If
      If tlLastError Then GoTo ErrorRut

      ' Process dates
      If InStr(rsModChars, Any "tzdy") And irIsDate(lsA) Then
         If InStr(rsModChars, "t") Then
            lsA = Parse$(lsA, " ", 1)
         ElseIf InStr(rsModChars, "d") Then
            lsA = Mid$(lsA, InStr(lsA, " ") + 1)
         ElseIf IsFalse Val(Remove$(Parse$(lsA, " ", 2), ":")) Then
            If InStr(rsModChars, "z") Then
               lsA = Parse$(lsA, " ", 1)
            Else
               lsA = ""
            End If
         End If
      End If

      ' Return it
      Function = lsA

   End If
   Exit Function

   ErrorRut:
   irHandleError "GetFieldX", rsModChars

End Function

'====================<[ Field Name Get Extended ]>=====================
Function slFNX Alias "slFNX" (rsColumnName As String, _
                              Optional ByVal rsModChars As String, _
                              Optional ByVal rlSetNumber As Long)Export As String
'   Returns the value for the field passed in ColumnName from the current
'   row for the set number passed in SetNumber.  Due to the frequent usage
'   of this routine it has a very short non-descript name.
'   ModChars:
'      Em = Return errors. This will override the global return errors flag.
'           m is the optional message display modifier and can be:
'              0 = No message is displayed.  This is the default.
'              1 = Display a warning message with OK button.  Error is
'                  returned when OK pressed.
'              2 = Display a question message with OK and Cancel buttons.
'                  If they press OK, error is returned.  If they press
'                  Cancel, will exit process.
'           If an error occurs then the return value will be an empty string.
'      e  = Do not return errors, display message and exit process. This
'          will override the global return errors flag.
'      N  = Return NULL fields as $NUL.  CAUTION: You can not distinguish
'          between a true NULL field and one that contains a single
'          $NUL char.
'      D  = Decrypt.
'      U  = Uncompress.
'      t  = If field is DateTime then do not return time.
'      z  = If field is DateTime then do not return time if zero.
'      d  = If field is DateTime then do not return date.
'      y  = If field is DateTime then return empty if time is zero.
'   CAUTION: Requesting to uncompress a field that is not compressed
'            will cause unpredicable results!

   Local llA As Long

   ' Audit set number
   If rlSetNumber < 0 Or rlSetNumber > UBound(thaSets) Or IsFalse thaSets(rlSetNumber) Then irHandleError "GetFieldNameX " & rsColumnName, rsModChars, %SQLitening_InvalidSetNumber : Exit Function

   ' Get column number
   llA = irGetColumnNumber(rsColumnName, rlSetNumber)
   If IsFalse llA Then irHandleError "GetFieldNameX " & rsColumnName, rsModChars, %SQLitening_InvalidColumnNameNumber : Exit Function

   ' Get field value
   Function = slFX(llA, rsModChars, rlSetNumber)

End Function

'========================<[ Get Change Count ]>========================
Function slGetChangeCount Alias "slGetChangeCount" (Optional ByVal rsModChars As String)Export As Long
'   Returns the number of rows that were changed.  If no ModChar T then
'   returns the number of rows that were changed or inserted or deleted
'   by the most recently completed SQL statement.  Changes caused by
'   triggers are not counted.  If ModChar T then returns the number of
'   row changes since open. Changes caused by triggers are included.
'   ModChars:
'      Em = Return errors. This will override the global return errors flag.
'           m is the optional message display modifier and can be:
'              0 = No message is displayed.  This is the default.
'              1 = Display a warning message with OK button.  Error is
'                  returned when OK pressed.
'              2 = Display a question message with OK and Cancel buttons.
'                  If they press OK, error is returned.  If they press
'                  Cancel, will exit process.
'      e  = Do not return errors, display message and exit process. This
'           will override the global return errors flag.
'      T  = Total changed since open.
'   Returns zero or the change count if processed OK. Else, depending on
'   ModChars and the global return errors flag, will either display error
'   and exit or will return -1.

   Local llChanges As Long
   Local lhRutAddr As Dword

   ' Init stuff
   Reset tlLastError

   If thDab Then        ' Must have an open database
      If InStr(rsModChars, "T") Then
         irGetRutAddress "sqlite3_total_changes", lhRutAddr
         Call Dword lhRutAddr Using UsingTotalChanges(thDab) To llChanges
      Else
         irGetRutAddress "sqlite3_changes", lhRutAddr
         Call Dword lhRutAddr Using UsingChanges(thDab) To llChanges
      End If
      Function = llChanges
   Else
      irHandleError "GetChangeCount", rsModChars, %SQLitening_NoOpenDatabase
      Function = -1
   End If

End Function

'========================<[ Get Column Count ]>========================
Function slGetColumnCount Alias "slGetColumnCount" (Optional ByVal rlSetNumber As Long)Export As Long
'   Returns the number of columns in set passed in SetNumber.  Will return
'   zero if an error occurs and the global return errors flag is on.

   ' Init stuff
   Reset tlLastError

   ' Audit set number
   If rlSetNumber < 0 Or rlSetNumber > UBound(thaSets) Or IsFalse thaSets(rlSetNumber) Then irHandleError "GetColumnCount", "", %SQLitening_InvalidSetNumber : Exit Function

   ' Return column count
   Function = twaColumnCounts(rlSetNumber)

End Function

'========================<[ Get Column Name ]>=========================
Function slGetColumnName Alias "slGetColumnName" (Optional ByVal rlColumnNumber As Long, _
                                                  Optional ByVal rlSetNumber As Long)Export As String
'   Returns either the name of a column or all column names contained in
'   the set passed in SetNumber.  Will return all column names if the
'   passed column number is omitted or zero. If all column names are
'   returned then it is a delimited text string which is $NUL seperated.
'   If ColumnNumber or SetNumber is invalid then will return an empty
'   string.  You may call slGetError or slGetErrorNumber to determine
'   the reason.

   ' Init stuff
   Reset tlLastError

   ' Audit set number and column number
   If rlSetNumber < 0 Or rlSetNumber > UBound(thaSets) Or IsFalse thaSets(rlSetNumber) Then tlLastError = %SQLitening_InvalidSetNumber : Exit Function
   If rlColumnNumber < 0 Or rlColumnNumber > twaColumnCounts(rlSetNumber) Then tlLastError = %SQLitening_InvalidColumnNameNumber : Exit Function

   Function = IIf$(rlColumnNumber, Parse$(Parse$(tsaNames(rlSetNumber), $Vt, 2), $Nul, rlColumnNumber), Parse$(tsaNames(rlSetNumber), $Vt, 2))

End Function

'=======================<[ Get Column Number ]>========================
Function slGetColumnNumber Alias "slGetColumnNumber" (rsColumnName As String, _
                                                      Optional ByVal rlSetNumber As Long)Export As Long
'   Returns the number of the column for the name that is passed in
'   ColumnName contained in the set passed in SetNumber.   Will return
'   zero if an error occurs and the global return errors flag is on.

   Local llA As Long

   ' Init stuff
   Reset tlLastError

   ' Audit set number
   If rlSetNumber < 0 Or rlSetNumber > UBound(thaSets) Or IsFalse thaSets(rlSetNumber) Then irHandleError "GetColumnNumber", "", %SQLitening_InvalidSetNumber : Exit Function

   ' Get column number
   llA = irGetColumnNumber(rsColumnName, rlSetNumber)
   If IsFalse llA Then irHandleError "GetColumnNumber", "", %SQLitening_InvalidColumnNameNumber : Exit Function

   ' Exit OK
   Function = llA

End Function

'===========================<[ Get Error ]>============================
Function slGetError Alias "slGetError" Export As String
'   Returns the error number and description of the last error that occurred.
'   Each command will initialize/set this value so you must call slGetError
'   before doing another command.  If error number is a minus value then
'   it is from SQLitening, if positive value then it is from SQLite3.
'   The returning format is:      number = description

   Local lhRutAddr As Dword
   Local lsA As String
   Local lzpError As Asciiz Ptr

   Select Case Long tlLastError
   Case %SQLitening_NoOpenDatabase : Function = "-7 = No open database"
   Case %SQLitening_AccessDenied : Function = "-8 = Access denied"
               '-- Connect was able to do Tcp Open but was unable to "talk"
               '   to SQLitening service. Probable caused by some type of
               '   firewall setting or an exit has denied connection.
               '-- Open, Attach, GetFile, or PutFile password check failed or
               '   the file is protected.
   Case %SQLitening_FileDoesNotExist : Function = "-9 = File does not exist"
   Case %SQLitening_FileOpenGetPutError : Function = "-10 = File error doing open, get, or put"
   Case %SQLitening_LockTimeout : Function = "-11 = Timeout occurred trying to set a lock"
   Case %SQLitening_NochangedRows : Function = "-12 = No changed rows"
   Case %SQLitening_InvalidColumnNameNumber : Function = "-13 = Invalid column name or number"
   Case %SQLitening_InvalidSetNumber : Function = "-14 = Invalid set number"
   Case %SQLitening_AttachIsInvalid : Function = "-15 = Can not use SQL Attach"
   Case %SQLitening_CanNotConnect : Function = "-16 = Can not connect"
                  '-- Tcp Open failed.
   Case %SQLitening_InvalidKeyOrNotFound : Function = "-17 = Invalid string/key or key not found"
               '-- Ini GetFile section or entity not found.
               '-- Ini PutFile WritePrivateProfileString winapi failed.
               '-- Encrypt key must be set and must be 16, 24, or 32 bytes.
   Case %SQLitening_SendOrReceiveError : Function = "-18 = Error sending or receiving message"
               '-- Prior connect failed, server went away, TCP time-out, or message length error
   Case %SQLitening_InvalidStringOrRequest : Function = "-19 = Invalid string or request"
               '-- Uncompressing string which is not compressed.
               '-- String is too big to compress.
               '-- BindDat is invalid.
               '-- RunProc is invalid library or entry.
   Case %SQLitening_ErrorAtServer : Function = "-20 = An error occured at server -- check server log."
   Case %SQLitening_MiscError : Function = "-21 = Miscellaneous error."
               '-- Copy database failed.
   Case Else
      ' Its not from SQLitening so get description from SQLite
      If IsTrue(thDab) And tlLastError >= 0 Then
         irGetRutAddress "sqlite3_errmsg", lhRutAddr
         ' Must handle differently if running remote
         If tuFlags.AreRunningRemote Then
            ' Running remote
            Call Dword lhRutAddr Using UsingGetErrorDescriptionRemote(thDab) To lsA
            lzpError = StrPtr(lsA)
         Else
            ' Running local
            Call Dword lhRutAddr Using UsingGetErrorDescriptionLocal(thDab) To lzpError
         End If
         Function = Format$(tlLastError) & " = " & IIf$(@lzpError = "not an error", "(description not available)", @lzpError)
      Else
         NoError:
         Function = Format$(tlLastError) & " = " & IIf$(tlLastError, "Unknown", "No") & " error"
      End If

   End Select

End Function

'========================<[ Get Error Number ]>========================
Function slGetErrorNumber Alias "slGetErrorNumber" Export As Long
'   Returns the error number of the last error that occurred. Each command
'   will initialize/set this value so you must call slGetErrorNumber
'   before doing another command.  If error number is a minus value then
'   it is from SQLitening, if positive value then it is from SQLite3.

   Function = tlLastError

End Function

'============================<[ Get File ]>============================
Function slGetFile Alias "slGetFile" (rsFileName As String, _
                                      wsFileData As String, _
                                      Optional ByVal rsModChars As String)Export As Long
'   FileName is the file name (Binary or Ini) you want to get.  Ini files
'   contain sections and entities.  Binary files can be any type of file,
'   including Ini files.  The FileName for Binary and Ini files may also
'   contain an optional password.  This password is separated from the file
'   name by the $BS character.  If it's a Binary file then the password may be
'   followed by a $BS and then a get position and get length which are
'   separated by a comma.  If for example the get position was 156 and the
'   length was 487 then 487 bytes at position 156 would be gotten into
'   FileData.  Using the get position and get length is useful when you only
'   want to get a portion of the file.  This also may be needed for very large
'   files even when you want to get the whole file.  If it's an Ini file then
'   append a $VT followed by the optional section name and entity name, $BS
'   delimited.  If section name is empty then will return all section names,
'   $NUL delimited.  If entity name is empty then will return all entity
'   names, $NUL delimited.  If entity name is not empty then will return the
'   value for that entity.
'   Binary Examples
'       1. filename [$BS password] -- will get binary file data
'       1. filename [$BS password][$BS get position, get length] --
'                  will get binary file data starting at get position
'                  for the get length.  Two $BS are required if you
'                  have get postion and get length but no password.
'   Ini Examples
'       2. filename [$BS password] $VT -- will get all Ini section names
'       3. filename [$BS password] $VT sectionname -- will get all Ini entity names
'       4. filename [$BS password] $VT sectionname $BS entityname -- will get value for Ini entity
'   ModChars:
'      Em = Return errors. This will override the global return errors flag.
'           m is the optional message display modifier and can be:
'              0 = No message is displayed.  This is the default.
'              1 = Display a warning message with OK button.  Error is
'                  returned when OK pressed.
'              2 = Display a question message with OK and Cancel buttons.
'                  If they press OK, error is returned.  If they press
'                  Cancel, will exit process.
'      e  = Do not return errors, display message and exit process. This
'           will override the global return errors flag.
'      S  = Open file with Shared lock, default is both Read and Write locks.
'           This only applies to standard files, not Ini.
'   Returns zero if processed OK. Else, depending on ModChars and the global
'   return errors flag, will either display error and exit or will return
'   the error number.
'                --- Local Mode ---
'   If the FileName is not fully pathed then it is assumed to be relative to
'   the current folder which is the same folder, unless changed by chdir, that
'   the first .Exe was run from.  If fully pathed then no assumptions are
'   made.  Since this running on a local computer SQLitening allows the files
'   to be located anywhere on the local hard drives or local network drives.
'   Examples: If your .Exe started in C:\Apps\MyApp then:
'   If FileName is X\Y\Able.Sld then will assume file is in C:\Apps\MyApp\X\Y.
'   If FileName is ..\Y\Able.Sld then will assume file is in C:\Apps\Y.
'   If FileName is C:\Able.Sld then no assumption.
'                 --- Remote Mode ---
'   The FileName is assumed to be relative to the folder which the service is
'   running from.  Since this is running on a remote server SQLitening can not
'   allow the user to access files anyplace on the server.  Access is denied
'   to any FileName that has a colon, a double dot, or begins with a
'   backslash.  This will insure that the file is in same folder as the
'   service or below it.  Access is always denied to SQLiteningServer.Cfg.
'   This is for security reasons since that is where passwords are stored.
'   Also for security reasons, all files must pre-exist.
'   Examples: If your service is running from C:\SQLitening then:
'   If FileName is Data\Able.Sld then will assume file is in C:\SQLitening\Data.
'   If FileName is ..\Y\Able.Sld then will get error %SQLitening_AccessDenied.
'   If FileName is C:\Able.Sld then will get error %SQLitening_AccessDenied.

   Local lhRutAddr As Dword
   Local llFileNumber As Long
   Local llFilePosition As Long
   Local llGetLength As Long
   Local lsFileName As String

   ' Init stuff
   Reset tlLastError

   ' Must handle differently if running remote
   If tuFlags.AreRunningRemote Then

      ' Running remote
      irGetRutAddress "SQLiteGetFile", lhRutAddr
      Call Dword lhRutAddr Using UsingGetFile(rsFileName, wsFileData, rsModChars) To tlLastError

   Else

      ' Running local
      lsFileName = Parse$(Parse$(rsFileName, $Vt, 1), $Bs, 1)

      ' File must exist
      If irIsFileThere(lsFileName) Then

         ' Seperate Ini from standard
         If InStr(rsFileName, $Vt) Then

            ' Ini file
            tlLastError = irGetIni(CurDir$ & "\" & lsFileName, Parse$(Parse$(rsFileName, $Vt, 2), $Bs, 1), Parse$(Parse$(rsFileName, $Vt, 2), $Bs, 2), wsFileData)

         Else

            ' Binary file
            llFileNumber = FreeFile
            If InStr(rsModChars, "S") Then
               Open Parse$(rsFileName, $Bs, 1) For Binary Lock Shared As llFileNumber
            Else
               Open Parse$(rsFileName, $Bs, 1) For Binary As llFileNumber
            End If
            If Err Then

               ' Will not open error
               tlLastError = %SQLitening_FileOpenGetPutError

            Else

               llFilePosition = Val(Parse$(Parse$(rsFileName, $Bs, 3), 1))
               llGetLength = Val(Parse$(Parse$(rsFileName, $Bs, 3), 2))
               If llFilePosition Then Seek llFileNumber, llFilePosition
               Get$ llFileNumber, IIf&(llGetLength, llGetLength, Lof(llFileNumber)), wsFileData
               If Err Then tlLastError = %SQLitening_FileOpenGetPutError
               Close llFileNumber

            End If

         End If

      Else

         ' File not there
         tlLastError = %SQLitening_FileDoesNotExist

      End If

   End If

   ' Check for error and exit
   If tlLastError Then irHandleError "GetFile """ & rsFileName & """", rsModChars
   Function = tlLastError

End Function

'===========================<[ Get Handle ]>===========================
Function slGetHandle Alias "slGetHandle" (Optional ByVal rsModChars As String, _
                                          Optional ByVal rlSetNumber As Long)Export As Dword
'   Returns the requested handle.  ModChars will determine which handle
'   is returned.  The database handle may be used to call SQLite directly
'   or can be passed to a different thread to be used in slOpen.  The set
'   handle may only be used to call SQLite directly and then only if in
'   local mode. A %SQLitening_InvalidStringOrRequest error will occur if
'   you try to get a set handle in remote mode.
'   ModChars:
'      D = Return the open database handle. This is the default.
'      S = Return the open set handle for set number passed in SetNumber.
'   Returns zero if an error occurs and the global return errors flag
'   is on.

   If InStr(rsModChars, "S") Then
      ' Can't request in in remote mode
      If tuFlags.AreRunningRemote Then irHandleError "GetHandle", "", %SQLitening_InvalidStringOrRequest : Exit Function
      ' Audit set number and return handle
      If rlSetNumber < 0 Or rlSetNumber > UBound(thaSets) Or IsFalse thaSets(rlSetNumber) Then irHandleError "GetHandle", "", %SQLitening_InvalidSetNumber : Exit Function
      Function = thaSets(rlSetNumber)
   Else
      ' Audit for open database and return handle
      If IsFalse thDab Then irHandleError "GetHandle", "", %SQLitening_NoOpenDatabase : Exit Function
      Function = thDab
   End If

End Function

'=========================<[ Get Insert ID ]>==========================
Function slGetInsertID Alias "slGetInsertID" ()Export As Quad
'   Returns the rowid key of the most recent insert into the database.
'   Returns zero if an error occurs and the global return errors flag
'   is on.

   Local llSetNumber As Long
   Local lhRutAddr As Dword
   Local lqA As Quad

   ' Init stuff
   Reset tlLastError

   If thDab Then        ' Must have an open database

      ' Must handle differently if running remote
      If tuFlags.AreRunningRemote Then

         ' Running remote
         irGetRutAddress "sqlite3_last_insert_rowid", lhRutAddr
         Call Dword lhRutAddr Using UsingLastInsertRowid(thDab) To lqA
         Function = lqA

      Else

         llSetNumber = slGetUnusedSetNumber
         If slSel("Select last_insert_rowid()", llSetNumber) = 0 Then
            If slGetRow(llSetNumber) Then Function = Val(slF(1, llSetNumber))
            slCloseSet llSetNumber
         End If

      End If

   Else

      ' No open database error
      irHandleError "GetInsertID", "", %SQLitening_NoOpenDatabase

   End If

End Function

'============================<[ Get Row ]>=============================
Function slGetRow Alias "slGetRow" (Optional ByVal rlSetNumber As Long, _
                                    Optional ByVal rsModChars As String)Export As Long
'   Gets the next selected row of fields for set passed in SetNumber.
'   Returns %True if there is a next row available.  Returns %False if
'   no row is available (end of set) and will close the set.  If an error
'   occurs then, depending on ModChars will either display error and exit
'   or will return %False. So if your code is handling errors and %False
'   is returned then you must use slGetErrorNumber to determine if there
'   was an error or no row is available.
'   ModChars:
'      Em = Return errors. This will override the global return errors flag.
'           m is the optional message display modifier and can be:
'              0 = No message is displayed.  This is the default.
'              1 = Display a warning message with OK button.  Error is
'                  returned when OK pressed.
'              2 = Display a question message with OK and Cancel buttons.
'                  If they press OK, error is returned.  If they press
'                  Cancel, will exit process.
'      e  = Do not return errors, display message and exit process. This
'           will override the global return errors flag.
'      C  = Close the set even if there is another row.  This is useful
'           when all you want to do is test if a set has any rows.

   Local llRC As Long

   ' Init stuff
   Reset tlLastError

   ' Audit set number
   If rlSetNumber < 0 Or rlSetNumber > UBound(thaSets) Or IsFalse thaSets(rlSetNumber) Then llRC = %SQLitening_InvalidSetNumber : GoTo ErrorRut

   ' Must handle differently if running remote
   If tuFlags.AreRunningRemote Then

      ' Running remote so next row
      Call Dword thCallNextRow Using UsingNextRow(thDab, rlSetNumber) To llRC

   Else

      ' Running local so step it
      Call Dword thCallStep Using UsingStepLocal(thaSets(rlSetNumber)) To llRC
      If llRC = %SQLITE3_ROW Then

         ' Returning $True
         llRC = %True

      ElseIf llRC = %SQLITE3_DONE Then

         ' Done so close this set
         Call Dword thCallFinalize Using UsingFinalizeLocal(thaSets(rlSetNumber))
         llRC = %False

      Else

         ' Unknown problem
         GoTo ErrorRut

      End If
   End If

   ' Check if there are more rows
   If llRC = -1 Then

      ' More rows so check if they want to close
      If InStr(rsModChars, "C") Then slCloseSet rlSetNumber

   ElseIf llRC = 0 Then

      ' Set was closed cause no more rows so reset set arrays
      Reset thaSets(rlSetNumber), twaColumnCounts(rlSetNumber), tsaNames(rlSetNumber)

   Else

      ErrorRut:
      irHandleError "GetRow", rsModChars, llRC : Exit Function

   End If

   ' Set retrun code
   Function = llRC

End Function

'===========================<[ Get Status ]>===========================
Function slGetStatus Alias "slGetStatus" (ByVal rlRequest As Long)Export As String
'   Returns the requested status which is normally a $NUL delimited list.
'   If an error occurs then will return an empty string.  You may call
'   slGetError or slGetErrorNumber to determine the reason.  Requests
'   1,2,4 will cause a trip to the server.
'   The following requests are currently valid:
'      1 = Return all named locks.  Will always return an empty string
'          if running in local mode.  Each named lock will be delimited
'          by vertical tabs ($VT). Lock data will be delimited by
'          backspaces ($BS) as follows:
'              1 = Tcp file number that owns the lock
'              2 = Lock value
'              3 = Status (0 = Locked, <>0 = Waiting)
'              4 = Time lock set (Milliseconds after midnight)
'              5 = User
'              6 = Computer
'              7 = IPAddress
'              8 = Connect Date-Time
'      2 = Return all connections.  Will always return an empty string
'          if running in local mode.  Each connection will be delimited
'          by vertical tabs ($VT).  Connection data will be delimited by
'          backspaces ($BS) as follows:
'              1 = Tcp file number
'              2 = User
'              3 = Computer
'              4 = IPAddress
'              5 = Connect Date-Time
'      3 = Return SQLitening flag settings.  Will return a comma delimited
'          list of one or zero for each flag as follows:
'              1 = One if AreRunningRemote is on for this thread.
'              2 = One if AreConnected is on for this thread.
'              3 = One if ReturnAllErrors is on.
'              4 = One if DisplayErrorMessage is on.
'              5 = One if DisplayErrorMessageQuestion is on.
'              6 = One if DoNotRetryIfBusy is on for this thread.
'      4 = Is Connection Active.  Will return "Yes" or "No"

   Local lhRutAddr As Dword
   Local lsStatus As String

   Select Case Long rlRequest
   Case 1, 2, 4
      ' Only used if running remote
      If tuFlags.AreRunningRemote Then

         ' Running remote
         irGetRutAddress "SQLiteGetStatus", lhRutAddr
         Call Dword lhRutAddr Using UsingGetStatus(thDab, rlRequest, lsStatus) To tlLastError

      End If

      ' Must reset stuff if request is 4 and connection is not active
      If rlRequest = 4 And lsStatus <> "Yes" Then
         lsStatus = "No"
         ' Clear running remote, set access mode to neutral,
         ' clear that we are connected, and clear database handle
         Reset tuFlags.AreRunningRemote, thSQLiteLibHand, tuFlags.AreConnected, thDab
      End If

   Case 3
      ' Return SQLitening flag settings
      lsStatus &= IIf$(tuFlags.AreRunningRemote, "1", "0") & ","
      lsStatus &= IIf$(tuFlags.AreConnected, "1", "0") & ","
      lsStatus &= IIf$(guFlags.ReturnAllErrors, "1", "0") & ","
      lsStatus &= IIf$(guFlags.DisplayErrorMessage, "1", "0") & ","
      lsStatus &= IIf$(guFlags.DisplayErrorMessageQuestion, "1", "0") & ","
      lsStatus &= IIf$(tuDbFlags.Bits.DoNotRetryIfBusy, "1", "0")
   End Select

   ' End rut
   Function = lsStatus

End Function

'=====================<[ Get Table Column Names ]>=====================
Function slGetTableColumnNames Alias "slGetTableColumnNames" (rsTableName As String)Export As String
'   Returns a list of column names contained in passed TableName.  The
'   names are returned as a delimited text string which is $NUL separated.
'   If the table name is invalid then will return an empty string. No
'   error will occur.

   Local llSetNumber As Long
   Local lsList As String

   ' Init stuff
   Reset tlLastError
   If IsFalse Len(Trim$(rsTableName)) Then Exit Function

   ' Get them
   Call slGetUnusedSetNumber To llSetNumber
   If slSel("Pragma table_info(" & rsTableName & ")", llSetNumber, "E") = 0 Then
      Do While slGetRow(llSetNumber)
         lsList = lsList & IIf$(Len(lsList), $Nul, "") & irGetFieldValue(2, llSetNumber)
      Loop
      Function = lsList
   End If

End Function

'========================<[ Get Table Names ]>=========================
Function slGetTableNames Alias "slGetTableNames" (Optional ByVal rsDataBase As String)Export As String
'   Returns a list of table names contained in passed DataBase. The
'   names are returned as a delimited text string which is $NUL
'   separated.  If DataBase is omitted or empty then will default to
'   Main.  The DataBase parm must be passed to get the table names
'   of attached databases.  If an error occurs then will return an
'   empty string.  You may call slGetError or slGetErrorNumber to
'   determine the reason.  Returns an empty string if an error occurs
'   and the global return errors flag is on.

   Local llSetNumber As Long
   Local lsList As String

   ' Init stuff
   Reset tlLastError

   ' Get them
   Call slGetUnusedSetNumber To llSetNumber
   If slSel("Select Name from " & IIf$(Len(rsDataBase), rsDataBase, "Main") & ".Sqlite_Master where Type='table' order by name", llSetNumber, "E") = 0 Then
      Do While slGetRow(llSetNumber)
         lsList = lsList & IIf$(Len(lsList), $Nul, "") & irGetFieldValue(1, llSetNumber)
      Loop
      Function = lsList
   End If

End Function

'=====================<[ Get Unused Set Number ]>======================
Function slGetUnusedSetNumber Alias "slGetUnusedSetNumber" Export As Long
'  Will return the lowest set number currently not being used.  No error
'  can occur.

   Local llSetNumber As Long

   ' Init stuff
   Reset tlLastError

   ' Get it
   Array Scan thaSets(), = 0, To llSetNumber
   If llSetNumber Then Decr llSetNumber Else llSetNumber = UBound(thaSets) + 1
   Function = llSetNumber

End Function

'======================<[ Is Column Name Valid ]>======================
Function slIsColumnNameValid Alias "slIsColumnNameValid" (rsColumnName As String, _
                                                          Optional ByVal rlSetNumber As Long)Export As Long
'   Returns %True if ColumnName is valid. Returns %False if it is not
'   valid. If an error occurs then, depending the global return errors
'   flag, will either display error and exit or will return %False.

   ' Init stuff
   Reset tlLastError

   ' Audit set number
   If rlSetNumber < 0 Or rlSetNumber > UBound(thaSets) Or IsFalse thaSets(rlSetNumber) Then irHandleError "IsColumnNameValid", "", %SQLitening_InvalidSetNumber : Exit Function

   ' Check name
   If irGetColumnNumber(rsColumnName, rlSetNumber) Then Function = %True

End Function

'=====================<[ Is Column Number Valid ]>=====================
Function slIsColumnNumberValid Alias "slIsColumnNumberValid" (ByVal rlColumnNumber As Long, _
                                                              Optional ByVal rlSetNumber As Long)Export As Long
'   Returns %True if ColumnNumber is valid. Returns %False if it is not
'   valid. If an error occurs then, depending on the global return errors
'   flag, will either display error and exit or will return %False.

   ' Init stuff
   Reset tlLastError

   ' Audit set number
   If rlSetNumber < 0 Or rlSetNumber > UBound(thaSets) Or IsFalse thaSets(rlSetNumber) Then irHandleError "IsColumnNumberValid", "", %SQLitening_InvalidSetNumber : Exit Function

   ' Check column number
   If rlColumnNumber > 0 And rlColumnNumber <= twaColumnCounts(rlSetNumber) Then Function = %True

End Function

'=========================<[ Is Field Null ]>==========================
Function slIsFieldNull Alias "slIsFieldNull" (ByVal rlColumnNumber As Long, _
                                              Optional ByVal rlSetNumber As Long)Export As Long
'   Returns %True if the type of field for the column passed in ColumnNumber
'   is Null. Returns %False if it is not Null. If an error occurs then,
'   depending on the global return errors flag, will either display error
'   and exit or will return %False.

   Local llFieldType As Long
   Local lhRutAddr As Dword

   ' Init stuff
   Reset tlLastError

   ' Audit set number and column number
   If rlSetNumber < 0 Or rlSetNumber > UBound(thaSets) Or IsFalse thaSets(rlSetNumber) Then irHandleError "IsFieldNull", "", %SQLitening_InvalidSetNumber : Exit Function
   If rlColumnNumber < 1 Or rlColumnNumber > twaColumnCounts(rlSetNumber) Then irHandleError "IsFieldNull", "", %SQLitening_InvalidColumnNameNumber : Exit Function

   ' Must handle differently if running remote
   If tuFlags.AreRunningRemote Then

      ' Running remote
      irGetRutAddress "SQLiteGetColumnType", lhRutAddr
      Call Dword lhRutAddr Using UsingGetColType(rlSetNumber, rlColumnNumber - 1) To llFieldType

   Else

      ' Running local
      irGetRutAddress "sqlite3_column_type", lhRutAddr
      Call Dword lhRutAddr Using UsingGetColType(thaSets(rlSetNumber), rlColumnNumber - 1) To llFieldType

   End If

   ' Determin return value
   Function = IIf&(llFieldType = 5, %True, %False)

End Function

'============================<[ Is Open ]>=============================
Function slIsOpen Alias "slIsOpen" ()Export As Long
'   Returns %True if there is a database open.

   If thDab Then Function = %True

End Function

'======================<[ Is Set Number Valid ]>=======================
Function slIsSetNumberValid Alias "slIsSetNumberValid" (Optional ByVal rlSetNumber As Long)Export As Long
'   Returns %True if SetNumber is valid or %False if SetNumber is invalid (not in
'   array or closed).  No error can occur.

   ' Init stuff
   Reset tlLastError

   ' Check if
   If rlSetNumber >= 0 And rlSetNumber <= UBound(thaSets) And thaSets(rlSetNumber) Then Function = %True

End Function

'==============================<[ Open ]>==============================
Function slOpen Alias "slOpen" (Optional ByVal rsFileName As String, _
                                Optional ByVal rsModChars As String)Export As Long
'   FileName is the SQLite file to open as a database. There are three
'   special file names:
'     1. ":memory:" = opens an empty tempory database in memory.
'     2. "" (empty or omitted) = opens an empty temporary database on disk.
'     3. All numeric database handle.  This is the formatted value returned
'        from slGetHandle or is the first parm received in a database Proc.
'        Using this in remote Procs allows for a database handle to be inherited
'        by SQLitening.Dll.  A flag will be set so this inherited database
'        handle can not be closed.
'   FileName may also contain an optional password.  This password
'   is separated from the file name by the $BS character.
'   ModChars:
'      C  = Create if not there. Ignored if ReadOnly.
'      Em = Return errors. This will override the global return errors flag.
'           m is the optional message display modifier and can be:
'              0 = No message is displayed.  This is the default.
'              1 = Display a warning message with OK button.  Error is
'                  returned when OK pressed.
'              2 = Display a question message with OK and Cancel buttons.
'                  If they press OK, error is returned.  If they press
'                  Cancel, will exit process.
'      e  = Do not return errors, display message and exit process. This
'           will override the global return errors flag.
'      f  = Do not enable foreign key support.  SQLitening enables foreign
'           key support by default by sending the following:
'                       PRAGMA foreign_keys = on
'      R  = ReadOnly.  You will not be able to update the database.
'      Tn = Where n is milliseconds to wait for a database lock.
'           Default is 10000 milliseconds (10 seconds). If running
'           in remote mode then the TCP SEND/RECV wait as set in
'           slConnect should be at least 3 times as long as this
'           wait.
'   Returns zero if processed OK. Else, depending on ModChars and the global
'   return errors flag, will either display error and exit or will return
'   the error number.
'                --- Local Mode ---
'   If the FileName is not fully pathed then it is assumed to be relative to
'   the current folder which is the same folder, unless changed by chdir, that
'   the first .Exe was run from.  If fully pathed then no assumptions are
'   made.  Since this running on a local computer SQLitening allows the files
'   to be located anywhere on the local hard drives or local network drives.
'   Examples: If your .Exe started in C:\Apps\MyApp then:
'   If FileName is X\Y\Able.Sld then will assume file is in C:\Apps\MyApp\X\Y.
'   If FileName is ..\Y\Able.Sld then will assume file is in C:\Apps\Y.
'   If FileName is C:\Able.Sld then no assumption.
'                 --- Remote Mode ---
'   The FileName is assumed to be relative to the folder which the service is
'   running from.  Since this is running on a remote server SQLitening can not
'   allow the user to access files anyplace on the server.  Access is denied
'   to any FileName that has a colon, a double dot, or begins with a
'   backslash.  This will insure that the file is in same folder as the
'   service or below it.
'   Examples: If your service is running from C:\SQLitening then:
'   If FileName is Data\Able.Sld then will assume file is in C:\SQLitening\Data.
'   If FileName is ..\Y\Able.Sld then will get error %SQLitening_AccessDenied.
'   If FileName is C:\Able.Sld then will get error %SQLitening_AccessDenied.
'   If FileName is X\Y\Able.Exe and command was GetFile then will get error %SQLitening_AccessDenied.

   Local llA As Long
   Local lhRutAddr As Dword
   Local lsFileName As String

   ' Init stuff
   Reset tuDbFlags, tlLastError
   lsFileName = Parse$(rsFileName, $Bs, 1)

   ' If not running remote and not in memory and not mod "C" then file must be there
   If IsFalse tuFlags.AreRunningRemote And _
      Len(lsFileName) And _
      UCase$(lsFileName) <> ":MEMORY:" And _
      Verify(lsFileName, "0123456789") And _
      IsFalse irIsFileThere(lsFileName) And _
      IsFalse InStr(rsModChars, "C") Then
      tlLastError = %SQLitening_FileDoesNotExist
      GoTo ErrorRut
   End If

   ' Load local libs if needed
   If IsFalse thSQLiteLibHand Then irLoadProcessingLibAndRuts %False

   ' Open it
   If Verify(lsFileName, "0123456789") Or IsFalse Len(lsFileName) Then
      ' File name passed
      irGetRutAddress "sqlite3_open_v2", lhRutAddr
      Call Dword lhRutAddr Using UsingOpen(IIf$(tuFlags.AreRunningRemote, rsFileName, lsFileName), _
                                           thDab, _
                                           IIf&(InStr(rsModChars, "R"), %SQLITE_OPEN_READONLY, %SQLITE_OPEN_READWRITE Or IIf&(InStr(rsModChars, "C"), %SQLITE_OPEN_CREATE, 0)), _
                                           0) To tlLastError
      If tlLastError <> %SQLITE3_OK Then GoTo ErrorRut
   Else
      ' Database handle inherited
      thDab = Val(lsFileName)
      tuDbFlags.Bits.DabInheritedSoCantClose = %True
   End If

   ' Set busy wait to what they passed or to a defalut of 1000 ms
   ' If remote then the default is already set in the open
   llA = InStr(rsModChars, "T")
   If llA Then
      llA = Val(Mid$(rsModChars, llA + 1))
   Else
      If IsFalse tuFlags.AreRunningRemote Then llA = 10000
   End If
   If llA Then
      irGetRutAddress "sqlite3_busy_timeout", lhRutAddr
      Call Dword lhRutAddr Using UsingSetBusyTimeout(thDab, llA)
   End If

   ' If running local then want to set enable load extension on
   If IsFalse tuFlags.AreRunningRemote Then
      irGetRutAddress "sqlite3_enable_load_extension", lhRutAddr
      Call Dword lhRutAddr Using UsingEnableLoadExtension(thDab, 1)
   End If

   ' Enable foreign key support
   If IsFalse InStr(rsModChars, "f") Then slExe "PRAGMA foreign_keys = ON"

   ' Exit OK
   Exit Function

   ErrorRut:
   irHandleError "Open """ & lsFileName & """", rsModChars
   Function = tlLastError

End Function

'==========================<[ Pop Database ]>==========================
Sub slPopDatabase Alias "slPopDatabase" (Optional rsSave As String)Export
'   Will pop(restore) the database handle and database flags from either
'   the internal stack or from passed Save.  If Save is omitted then data
'   will be obtained from the internal stack.  If Save is passed then if
'   must be formated same as in slPushDatabase.   No error can occur.

   Local lsData As String

   ' Init stuff
   Reset tlLastError
   If VarPtr(rsSave) Then
      lsData = rsSave
   Else
      If tlStackIX Then
         lsData = tsaStack(tlStackIX)
         Decr tlStackIX
      End If
   End If

   ' Restore data
   thDab = Val(Parse$(lsData, 1))
   tuDbFlags.tuDbFlags = Val(Parse$(lsData, 2))

End Sub

'============================<[ Pop Set ]>=============================
Sub slPopSet Alias "slPopSet" (Optional ByVal rlSetNumber As Long, _
                               Optional rsSave As String)Export
'   Will pop(restore) the set data from either the internal stack or from
'   the passed Save.  If Save is omitted or zero then the data will be
'   obtained from the internal stack.  If an invalid SetNumber is passed
'   then will exit process.

   Local lsData As String

   ' Audit set number
   If rlSetNumber < 0 Or rlSetNumber > UBound(thaSets) Then irHandleError "PopSet", "", %SQLitening_InvalidSetNumber : Exit Sub

   ' Init stuff
   If IsMissing(rsSave) Then
      If tlStackIX Then
         lsData = tsaStack(tlStackIX)
         Decr tlStackIX
      End If
   Else
      lsData = rsSave
   End If

   ' Restore data
   thaSets(rlSetNumber) = Val(Parse$(lsData, $Bs, 1))
   twaColumnCounts(rlSetNumber) = Val(Parse$(lsData, $Bs, 2))
   tsaNames(rlSetNumber) = Parse$(lsData, $Bs, 3)

End Sub

'=========================<[ Push Database ]>==========================
Sub slPushDatabase Alias "slPushDatabase" (Optional wsSave As String)Export
'   Will push(save) the database handle and database flags to either the
'   internal stack or return it in passed Save. If Save is omitted then the
'   handle will be placed on the internal stack.  The current database
'   handle is forgotten but not closed.  A slPopDatabase is required
'   before reusing this database.  No error can occur.

   Local lsData As String

   ' Init stuff
   lsData = Format$(thDab) & "," & Format$(tuDbFlags.tuDbFlags)
   Reset thDab, tuDbFlags, tlLastError

   ' Place the data
   If VarPtr(wsSave) Then
      wsSave = lsData
   Else
      Incr tlStackIX
      If tlStackIX > UBound(tsaStack) Then ReDim Preserve tsaStack(1 To tlStackIX)
      tsaStack(tlStackIX) = lsData
   End If

End Sub

'============================<[ Push Set ]>============================
Sub slPushSet Alias "slPushSet" (Optional ByVal rlSetNumber As Long, _
                                Optional wsSave As String)Export
'   Will push (save) the data about an open set to either the internal
'   stack or return it in the passed Save.  If Save is omitted then the
'   data will be placed on the internal stack.  A slPopSet is required
'   before reusing this set.  If an invalid SetNumber is passed then will
'   exit process.

   Local lsData As String

   ' Audit set number
   If rlSetNumber < 0 Then irHandleError "PushSet", "", %SQLitening_InvalidSetNumber : Exit Sub

   ' Redim arrays if required
   If rlSetNumber > UBound(thaSets) Then
      ReDim Preserve thaSets(0 To rlSetNumber)
      ReDim Preserve twaColumnCounts(0 To rlSetNumber)
      ReDim Preserve tsaNames(0 To rlSetNumber)
   End If

   ' Init stuff
   lsData = Format$(thaSets(rlSetNumber)) & $Bs & Format$(twaColumnCounts(rlSetNumber)) & $Bs & tsaNames(rlSetNumber)
   Reset thaSets(rlSetNumber), twaColumnCounts(rlSetNumber), tsaNames(rlSetNumber)

   ' Place the handle
   If IsMissing(wsSave) Then
      Incr tlStackIX
      If tlStackIX > UBound(tsaStack) Then ReDim Preserve tsaStack(1 To tlStackIX)
      tsaStack(tlStackIX) = lsData
   Else
      wsSave = lsData
   End If

End Sub

'============================<[ Put File ]>============================
Function slPutFile Alias "slPutFile" (rsFileName As String, _
                                      rsFileData As String, _
                                      Optional ByVal rsModChars As String)Export As Long
'   FileName is the file name (Binary or Ini) that you want to put.  Ini files
'   contain sections and entities.  Binary files can be any type of file(text,
'   random, image, etc) conatining any type of data.  The FileName for Binary
'   and Ini files may also contain an optional password.  This password is
'   separated from the file name by the $BS character.  If it's a Binary file
'   then the password may be followed by a $BS and then a put position.  If
'   for example the put position was 156 and the length of FileData was 487
'   then 487 bytes at position 156 would be put (replaced/updated).  Use the
'   sepecial position of -1 when you want to put or append to the end of the
'   file.  Using the put position is useful when you only want to put a
'   portion of the file.  This also may be needed for very large files even
'   when you want to put the whole file (put in multiple chunks).  If it's an
'   Ini file then append a $VT followed by the section name and entity name,
'   $BS delimited.  The section name is required.  If entity name is empty
'   then the section will be deleted.  FileData will contain either the
'   standard file data or an Ini entity value.  If the Ini entity value in
'   FileData is omitted then the entity entry, name and value, will be deleted
'   (this is standard Windows Ini file processing).
'   Binary Examples
'       1. filename [$BS password] -- will put binary file data
'       1. filename [$BS password][$BS put position] -- will put binary file data
'                  starting at put position.  Two $BS are required if you have put
'                  position but no password.
'   Ini Examples
'       1. filename [$BS password] $VT sectionname -- sectionname will be deleted
'       2. filename [$BS password] $VT sectionname $BS entityname -- if FileData
'                 is empty then will delete the entityname entry else will add or
'                 update the entityname entry.
'   ModChars:
'      C  = Create file if not already there.
'      D  = Delete binary file if length is zero after put is done.
'      Em = Return errors. This will override the global return errors flag.
'           m is the optional message display modifier and can be:
'              0 = No message is displayed.  This is the default.
'              1 = Display a warning message with OK button.  Error is
'                  returned when OK pressed.
'              2 = Display a question message with OK and Cancel buttons.
'                  If they press OK, error is returned.  If they press
'                  Cancel, will exit process.
'      e  = Do not return errors, display message and exit process. This
'           will override the global return errors flag.
'      S  = Open file with Shared lock, default is both Read and Write locks.
'           This only applies to standard files, not Ini.
'      T =  Truncate file after putting when it's a binary file and you passed
'           a put position.  The default for binary files is to truncate if you
'           do not pass a put position and to not truncate if you pass a put
'           position.
'   Returns zero if processed OK. Else, depending on ModChars and the global
'   return errors flag, will either display error and exit or will return
'   the error number.
'                --- Local Mode ---
'   If the FileName is not fully pathed then it is assumed to be relative to
'   the current folder which is the same folder, unless changed by chdir, that
'   the first .Exe was run from.  If fully pathed then no assumptions are
'   made.  Since this running on a local computer SQLitening allows the files
'   to be located anywhere on the local hard drives or local network drives.
'   Examples: If your .Exe started in C:\Apps\MyApp then:
'   If FileName is X\Y\Able.Sld then will assume file is in C:\Apps\MyApp\X\Y.
'   If FileName is ..\Y\Able.Sld then will assume file is in C:\Apps\Y.
'   If FileName is C:\Able.Sld then no assumption.
'                 --- Remote Mode ---
'   The FileName is assumed to be relative to the folder which the service is
'   running from.  Since this is running on a remote server SQLitening can not
'   allow the user to access files anyplace on the server.  Access is denied
'   to any FileName that has a colon, a double dot, or begins with a
'   backslash.  This will insure that the file is in same folder as the
'   service or below it. Access is denied to files that are in the Temp folder
'   (contains the Row Data Chunk overflows), any file whose extension is
'   .Exe, .Dll, .Log or .Cfg that reside in same folder as the service.  Also
'   for security reasons, all files must be included in the FACT  of the server
'   config file. This is because of the damage that can be done due to a coding
'   error with PutFile.
'   Examples: If your service is running from C:\SQLitening then:
'   If FileName is Data\Able.Sld then will assume file is in C:\SQLitening\Data.
'   If FileName is ..\Y\Able.Sld then will get error %SQLitening_AccessDenied.
'   If FileName is C:\Able.Sld then will get error %SQLitening_AccessDenied.
'   If FileName is Able.Exe then will get error %SQLitening_AccessDenied.
'   If FileName is Temp\Goffy.Sld then will get error %SQLitening_AccessDenied.

   Local llFileNumber As Long
   Local llFilePosition As Long
   Local lhRutAddr As Dword
   Local lsFileName As String

   ' Init stuff
   Reset tlLastError

   ' Must handle differently if running remote
   If tuFlags.AreRunningRemote Then

      ' Running remote
      irGetRutAddress "SQLitePutFile", lhRutAddr
      Call Dword lhRutAddr Using UsingPutFile(rsFileName, rsFileData, rsModChars) To tlLastError

   Else

      ' Running local
      lsFileName = Parse$(Parse$(rsFileName, $Vt, 1), $Bs, 1)
      llFilePosition = Val(Parse$(Parse$(rsFileName, $Vt, 1), $Bs, 3))

      ' File must exist or must have "C" mod char
      If irIsFileThere(lsFileName) Or InStr(rsModChars, "C") Then

         ' Separate Ini from Binary
         If InStr(rsFileName, $Vt) Then

            ' Ini file
            If IsFalse WritePrivateProfileString(Parse$(Parse$(rsFileName, $Vt, 2), $Bs, 1), _
                                                 Parse$(Parse$(rsFileName, $Vt, 2), $Bs, 2), ByCopy rsFileData, CurDir$ & "\" & lsFileName) Then tlLastError = %SQLitening_InvalidKeyOrNotFound

         Else

            ' Binary file
            llFileNumber = FreeFile
            If InStr(rsModChars, "S") Then
               Open lsFileName For Binary Lock Shared As llFileNumber
            Else
               Open lsFileName For Binary As llFileNumber
            End If
            If Err Then

               ' Will not open error
               tlLastError = %SQLitening_FileOpenGetPutError

            Else

               If llFilePosition = -1 Then llFilePosition = Lof(llFileNumber) + 1
               If llFilePosition Then Seek llFileNumber, llFilePosition
               Put$ llFileNumber, rsFileData
               If IsFalse llFileNumber Or InStr(rsModChars, "T") Then SetEof llFileNumber
               If Err Then tlLastError = %SQLitening_FileOpenGetPutError

               ' Delete file if requested and length is zero
               If InStr(rsModChars, "D") And IsFalse Lof(llFileNumber) Then
                  Close llFileNumber
                  Kill lsFileName
                  If irIsFileThere(lsFileName) Then tlLastError = %SQLitening_FileOpenGetPutError
               Else
                  Close llFileNumber
               End If

            End If
         End If

      Else

         ' File not there
         tlLastError = %SQLitening_FileDoesNotExist

      End If

   End If

   ' Check for error and exit
   ExitRut:
   If tlLastError Then irHandleError "PutFile """ & rsFileName & """", rsModChars
   Function = tlLastError

End Function

'============================<[ Run Proc ]>============================
Function slRunProc Alias "slRunProc" (rsProcName As String, _
                                      blParm1 As Long, _
                                      blParm2 As Long, _
                                      bsParm3 As String, _
                                      bsParm4 As String, _
                                      Optional ByVal rsModChars As String)Export As Long
'   ProcName is the one character library suffix followed by the name of the
'   entry to be called within the library.  ProcName may also contain an
'   optional password.  This password is separated from the proc name by the
'   $BS character.     The full library name is SQLiteningProcs + the one
'   character suffix.  A ProcName of AStoreOrder^TallWalk (^ is the $BS)
'   would have the following three parts:
'      1.  A is the library name suffix so SQLiteningProcsA.Dll would be
'          loaded.
'      2.  StoreOrder is the entry name.
'      3.  TallWalk is the optional password.
'   Normally when you RunProc the library is loaded, the proc is run, and then
'   the library is unloaded.  This is the default, you may override that with
'   the L, U, and u ModChars.  Note that a library that creates SQLite custom
'   functions must remain loaded.  If you are only loading or unloading a
'   library (L or U ModChar passed) then the ProcName is only the one character
'   library suffix. Note also that if you fail to unload a library and you are
'   running remotely, it will remain loaded in memory until the service is stopped.
'   SQLiteningProcsA.Bas is included as a sample proc library.  There are several
'   sample entries coded there including how to create SQLite custom functions.
'   Some examples are called by this slRunProc while one example is invoked by
'   using the SQLite load_extension function.
'   ModChars:
'      Em = Return errors. This will override the global return errors flag.
'           m is the optional message display modifier and can be:
'              0 = No message is displayed.  This is the default.
'              1 = Display a warning message with OK button.  Error is
'                  returned when OK pressed.
'              2 = Display a question message with OK and Cancel buttons.
'                  If they press OK, error is returned.  If they press
'                  Cancel, will exit process.
'      e  = Do not return errors, display message and exit process. This
'           will override the global return errors flag.
'      L  = Load the library.  Pass only the one character library prefix
'           in ProcName.  Useful if you know you will be running procs many
'           times from this library. You must later unload the library.
'           CAUTION!! If you fail to unload it then SQLiteningServer will
'                     have to be stopped to get it unloaded.
'      U  = Unload the library.  Pass only the one character library prefix
'           in ProcName.  Required if your previously loaded the library.
'      u  = Do not unload the library after running the proc. Pass the one
'           character library prefix and entry name in ProcName.  You must
'           later unload the library using the U ModChar.
'           CAUTION!! If you fail to unload it then SQLiteningServer will
'                     have to be stopped to get it unloaded.

   Local lhProcsLibHand As Dword
   Local lhRutAddr As Dword

   ' Must handle differently if running remote
   If tuFlags.AreRunningRemote Then

      ' Running remote so run remote proc
      irGetRutAddress "SQLiteRunProc", lhRutAddr
      Call Dword lhRutAddr Using UsingRunProcRemote(thDab, rsProcName, blParm1, blParm2, bsParm3, bsParm4, rsModChars) To tlLastError

   Else

      ' Running local so check if they passed an entry name
      If Len(Mid$(rsProcName, 2)) Then

         ' Yes we have an entry name so load and get address
         lhProcsLibHand = LoadLibrary("SQLiteningProcs" & Left$(rsProcName, 1))
         lhRutAddr = GetProcAddress(lhProcsLibHand, ByCopy Parse$(Mid$(rsProcName, 2), $Bs, 1))
         If lhRutAddr Then
            Call Dword lhRutAddr Using UsingRunProcLocal(thDab, 0, blParm1, blParm2, bsParm3, bsParm4) To tlLastError
         Else
            tlLastError = %SQLitening_InvalidStringOrRequest
         End If
         ' Free the lib if we can
         If IsFalse InStr(rsModChars, "u") Then FreeLibrary(lhProcsLibHand)
      Else

         ' No entry name then just load or free lib
         If InStr(rsModChars, "L") Then
            lhProcsLibHand = LoadLibrary("SQLiteningProcs" & Left$(rsProcName, 1))
         ElseIf InStr(rsModChars, "U") Then
            lhProcsLibHand = GetModuleHandle("SQLiteningProcs" & rsProcName)
            FreeLibrary(lhProcsLibHand)
         End If
      End If
   End If
   If tlLastError Then GoTo ErrorRut

   ' Exit OK
   Exit Function

   ErrorRut:
   irHandleError "RunProc " & rsProcName, rsModChars
   Function = tlLastError

End Function

'==============================<[ Sel ]>===============================
Function slSel Alias "slSel" (rsStatement As String, _
                              Optional ByVal rlSetNumber As Long, _
                              Optional ByVal rsModChars As String)Export As Long
'   Will prep the passed Statement and if no errors will activate the
'   passed SetNumber (must have been closed/inactive).  This SetNumber
'   can then be used in slGetRow and FieldGet (slF, slFN, slFX, slFNX)
'   commands to get rows and data.  The Statement should be Select or
'   Pragma or any other that returns rows (Insert, Update, and Delete do
'   not return rows).  SetNumber can be omitted or be any value from 0 to
'   32767. If omitted then will use zero.  If omitted or is zero then will
'   first do a slCloseSet.  Since SetNumber is used as an array index, no
'   gaps is best, which will result in a smaller array. You can have as
'   many unique sets open/active at same time as your memory will allow.
'   ModChars:
'      C  = Will first do a slCloseSet(rlSetNumber). This will prevent
'           error 14 (%SQLitening_InvalidSetNumber) but should be used
'           with caution. Omiting set number or passing set number of
'           zero will do the same thing.
'      D  = Allow duplicate column names. Not recommended if using slFN or slFNX.
'           because you will always get the first value returned.  SQLite does
'           not normally return qualified column names.  SQLite will return C1
'           twice if you Select T1.C1, T2.C1. So the solution is to alias one of
'           them with the As clause as follows Select T1.C1, T2.C1 as C1Again.
'           There is a Pragma called "full_column_names" which forces SQLite to
'           return qualified names, but does not seem to work if you Select *.
'           Read up on it and use if you like.  I like using an alias because it
'           is less code and more clear.
'      Em = Return errors. This will override the global return errors flag.
'           m is the optional message display modifier and can be:
'              0 = No message is displayed.  This is the default.
'              1 = Display a warning message with OK button.  Error is
'                  returned when OK pressed.
'              2 = Display a question message with OK and Cancel buttons.
'                  If they press OK, error is returned.  If they press
'                  Cancel, will exit process.
'      e  = Do not return errors, display message and exit process. This
'           will override the global return errors flag.
'      Bn = Do a Begin Transaction before doing the Sel command. The type
'           of Begin is controlled by the value of n as follows:
'              0 = Deferred. This is the default if n is omitted.
'              1 = Immediate.
'              2 = Exclusive.
'           This allows for database locking and selecting in one trip to the server.
'           CAUTION: If the Begin or the Select returns Busy then will restart with
'                    the Begin.  Use Begin Immediate to prevent this or set ProcessMods
'                    to %gbfDoNotRetryIfBusy.
'      R =  Release all named locks owned by this connection after doing
'           the Sel.
'   Returns zero if processed OK. Else, depending on ModChars and the global
'   return errors flag, will either display error and exit or will return
'   the error number.

   Local llDo As Long
   Local lsA As String
   Local lsColumnNames As String
   Local lzpA As Asciiz Ptr

   ' Close set if requested
   If InStr(rsModChars, "C") Or IsFalse rlSetNumber Then slCloseSet(rlSetNumber)

   ' Do pre select stuff
   If IsFalse irPreSelect(rsStatement, rlSetNumber) Then ErrorRut

   ' Must handle differently if running remote
   If tuFlags.AreRunningRemote Then
      ' Running remote so do remote select
      Do
         Call Dword thCallSel Using UsingSel(thDab, rsStatement, rlSetNumber, rsModChars, lsColumnNames) To tlLastError
         If tlLastError And IsFalse irCheckIfBusy("Sel " & rsStatement) Then ErrorRut
      Loop While tlLastError = %SQLITE3_BUSY

      ' Mark the set as active
      thaSets(rlSetNumber) = 1

   Else

     ' Running local so.......
      ' Do Begin if requested
      llDo = InStr(rsModChars, "B")
      If llDo Then
         lsA = "Begin " & IIf$(Val(Mid$(rsModChars, llDo + 1)) = 2, "Exclusive", IIf$(Val(Mid$(rsModChars, llDo + 1)) = 1, "Immediate", ""))
         Do
            Call Dword thCallExe Using UsingExe(thDab, StrPtr(lsA), 0, 0, 0) To tlLastError
            If tlLastError And IsFalse irCheckIfBusy("Sel " & rsStatement) Then GoTo ErrorRut
         Loop While tlLastError = %SQLITE3_BUSY
      End If

      ' Prep statement
      Do
         Call Dword thCallPrepare Using UsingPrepareLocal(thDab, StrPtr(rsStatement), Len(rsStatement) + 1, thaSets(rlSetNumber), 0) To tlLastError
         If tlLastError And IsFalse irCheckIfBusy("Sel " & rsStatement) Then GoTo ErrorRut
      Loop While tlLastError = %SQLITE3_BUSY

      ' Build list of names
      Reset llDo
      Do
         If thaSets(rlSetNumber) Then Call Dword thCallGetColumnName Using UsingGetColName(thaSets(rlSetNumber), llDo) To lzpA
         If IsFalse Len(@lzPa) Then Exit Do
         lsColumnNames = lsColumnNames & IIf$(Len(lsColumnNames), $Nul, "") & @lzpA
         Incr llDo
      Loop

   End If

   ' Do post select stuff
   If IsFalse irPostSelect(lsColumnNames, rlSetNumber, rsModChars) Then ErrorRut

   ' Exit OK
   Exit Function

   ErrorRut:
   irHandleError "Sel " & rsStatement, rsModChars
   Function = tlLastError

End Function

'============================<[ Sel Ary ]>=============================
Function slSelAry Alias "slSelAry" (rsStatement As String, _
                                    wsaData() As String, _
                                    Optional ByVal rsModChars As String)Export As Long
'   Loads the passed Data array with all the column data from each row
'   which is returned by the select statement passed in Statement.  The
'   returned array will be one or two dimensions.
'   If the 'Q' ModChar is not passed then the array will be two decisions
'   where the first dimension is equal to the number of columns while the
'   second is equal to the number of rows (plus one if column names are
'   returned).
'   If the 'Q' ModChar is passed then the array is one dimension with an entry
'   for each row (plus one if column names are returned) and the columns will
'   be delimited by the ascii value following the 'Q' ModChar.  Be sure that
'   none of your returning data may contain the delimiter character.
'   If a 'c' ModChar is not passed then the index of the first row is zero and
'   will contain the column names (lbound = 0).  If a 'c' ModChar is passed
'   then the index of the first row is one and no column names are passed
'   (lbound = 1). Regardless of the 'c' ModChar, the first row data is always
'   index one.
'   Beware that very large record sets will consume lots of memory.
'   ModChars:
'      Em = Return errors. This will override the global return errors flag.
'           m is the optional message display modifier and can be:
'              0 = No message is displayed.  This is the default.
'              1 = Display a warning message with OK button.  Error is
'                  returned when OK pressed.
'              2 = Display a question message with OK and Cancel buttons.
'                  If they press OK, error is returned.  If they press
'                  Cancel, will exit process.
'      e  = Do not return errors, display message and exit process. This
'           will override the global return errors flag.
'      Bn = Do a Begin Transaction before doing the Sel command. The type
'           of Begin is controlled by the value of n as follows:
'              0 = Deferred. This is the default if n is omitted.
'              1 = Immediate.
'              2 = Exclusive.
'           This allows for database locking and selecting in one trip to the server.
'           CAUTION: If the Begin or the Select returns Busy then will restart with
'                    the Begin.  Use Begin Immediate to prevent this or set ProcessMods
'                    to %gbfDoNotRetryIfBusy.
'      Q# = Return a one dimension array where each column is delimited by a
'           single character.  That character is determined by the ascii value
'           at #. The default is to return a two dimension array.
'      R =  Release all named locks owned by this connection after doing
'           the SelAry.
'      c =  Do not return the column names as a row of data at index zero.  The
'           default is to return the column names as row zero.
'      D$ = Decrypt.
'      U$ = Uncompress.
'      N$ = Return NULL fields as $NUL.  CAUTION: You can not distinguish
'           between a true NULL field and one that contains a single
'           $NUL char.
'      t$ = If field is DateTime then do not return time.
'      z$ = If field is DateTime then do not return time if zero.
'      d$ = If field is DateTime then do not return date.
'      y$ = If field is DateTime then return empty if time is zero.
'   Note: The above $ is a comma delimited list of column numbers. Beware
'         that this columns apply to ALL rows.  So if, for example, some
'         times a column is compressed and sometimes not then do not use
'         slSelAry, rather build your own array using slSel.
'   Example: slSelAry "Select F1, F2 from T1", A(), "D1,2N2" would
'            decrypt columns 1 and 2 and also return $NUL if column 2
'            is Null. If table T1 had 3 rows then the array dimensions
'            would  be (1 to 2, 0 to 3).  A(1,0) would contain "F1".
'            A(2,0) would contain "F2".  A(1,1) would contain the value
'            of F1 from row 1. A(2,1) would contain the value of F2 from
'            row 1. Etc....
'   Example: slSelAry "Select F1, F2 from T1", A(), "Q9c" would return
'            a one dimension array with the columns tab delimited and no
'            column names in index zero.
'   Returns zero if processed OK. Else, depending on ModChars and the global
'   return errors flag, will either display error and exit or will return
'   the error number.

   Local llDo, llDo2 As Long
   Local llA As Long
   Local llSetNumber As Long
   Local llColCount As Long
   Local llRowNumber As Long
   Local lbOneDim As Byte
   Local lbNoColNames As Byte
   Local lsA As String
   Local lsDelimiter As String
   Local lsPossibleModChars As String
   Local lsaColModChars() As String

   ' Init stuff
   lsPossibleModChars = "BcDdEeNQRtUyz"
   If irCheckStatemetForAttachAndOpenDatabase(rsStatement) Then GoTo ErrorRut
   llA = InStr(rsModChars, "Q")
   If llA Then
      lbOneDim = %True
      lsDelimiter = Chr$(Val(Mid$(rsModChars, llA + 1)))
   End If
   If InStr(rsModChars, "c") Then lbNoColNames = 1

   ' Determine the modchars to pass to slSel
   lsA = "E" & IIf$(InStr(rsModChars, "R"), "R", "")
   llDo = InStr(rsModChars, "B")
   If llDo Then lsA &= "B" & Format$(Val(Mid$(rsModChars, llDo + 1)))

   ' Do Select and get column count
   Call slGetUnusedSetNumber To llSetNumber
   tlLastError = slSel(rsStatement, llSetNumber, lsA)
   If tlLastError <> %SQLITE3_OK Then GoTo ErrorRut
   llColCount = slGetColumnCount(llSetNumber)

   ' Start the array
   If lbOneDim Then ReDim wsaData(lbNoColNames To 5000) Else ReDim wsaData(1 To llColCount, lbNoColNames To 5000)

   ' Load the column modchars array
   Dim lsaColModChars(1 To llColCount)
   For llDo = 1 To Len(lsPossibleModChars)
      llA = InStr(rsModChars, Mid$(lsPossibleModChars, llDo, 1))
      If llA Then
         lsA = Parse$(Mid$(rsModChars, llA + 1), Any lsPossibleModChars, 1)
         For llDo2 = 1 To ParseCount(lsA)
            llA = Val(Parse$(lsA, llDo2))
            If llA > 0 And llA <= UBound(lsaColModChars) Then lsaColModChars(llA) = lsaColModChars(llA) & Mid$(lsPossibleModChars, llDo, 1)
         Next
      End If
   Next

   ' Load the column names as row zero if needed
   If IsFalse lbNoColNames Then
      For llDo = 1 To llColCount
         If lbOneDim Then
            ' Append with delimiter
            wsaData(0) += IIf$(llDo > 1, lsDelimiter, "") &  slGetColumnName(llDo, llSetNumber)
         Else
            ' Put into array
            wsaData(llDo, 0) = slGetColumnName(llDo, llSetNumber)
         End If
      Next
   End If

   ' Get each row
   Do While slGetRow(llSetNumber)

      ' Bump row number and expand array if needed
      Incr llRowNumber
      If llRowNumber > UBound(wsaData(IIf&(lbOneDim, 1, 2))) Then If lbOneDim Then ReDim Preserve wsaData(lbNoColNames To llRowNumber + 2500) Else ReDim Preserve wsaData(1 To llColCount, lbNoColNames To llRowNumber + 2500)

      ' Load each field for this row
      For llDo = 1 To llColCount
         If lbOneDim Then
            ' Append with delimiter
            wsaData(llRowNumber) += IIf$(llDo > 1, lsDelimiter, "") &  slFX(llDo, lsaColModChars(llDo), llSetNumber)
         Else
            ' Put into array
            wsaData(llDo, llRowNumber) = slFX(llDo, lsaColModChars(llDo), llSetNumber)
         End If
      Next
   Loop

   ' Erase array if nothing to return
   If lbNoColNames And IsFalse llRowNumber Then
      Erase wsaData()
   Else
      ' Set final array size
      If lbOneDim Then ReDim Preserve wsaData(lbNoColNames To llRowNumber) Else ReDim Preserve wsaData(1 To llColCount, lbNoColNames To llRowNumber)
   End If

   ' Exit OK
   Exit Function

   ErrorRut:
   irHandleError "slSel" & IIf$(InStr(rsModChars, "_"), "Str", "Ary") & " " & rsStatement, rsModChars
   Function = tlLastError

End Function

'============================<[ Sel Str ]>=============================
Function slSelStr Alias "slSelStr" (rsStatement As String, _
                                    Optional ByVal rsModChars As String)Export As String
'   Returns selected rows as a delimited text string.  Each field and
'   record is delimited by a single character.  The default field delimiter
'   is $BS while the default record delimiter is $VT.  These defaults can
'   be change using the Q ModChar (see below).  You must be sure that none
'   of the returning data contains either of these delimiters.  Also,
'   beware that very large record sets will consume lots of memory.
'   ModChars:
'      C =  Return the column names as the first row of data.  The default
'           is to not return the column names.
'      Em = Return errors. This will override the global return errors flag.
'           m is the optional message display modifier and can be:
'              0 = No message is displayed.  This is the default.
'              1 = Display a warning message with OK button.  Error is
'                  returned when OK pressed.
'              2 = Display a question message with OK and Cancel buttons.
'                  If they press OK, error is returned.  If they press
'                  Cancel, will exit process.
'           If this is passed and an error occurs then will return an
'           empty string.  You can not determine if the empty return value
'           is valid or invalid without calling one of the GetError routines.
'           You may call slGetError or slGetErrorNumber to determine the reason.
'      e  = Do not return errors, display message and exit process. This
'           will override the global return errors flag.
'      Bn = Do a Begin Transaction before doing the Sel command. The type
'           of Begin is controlled by the value of n as follows:
'              0 = Deferred. This is the default if n is omitted.
'              1 = Immediate.
'              2 = Exclusive.
'           This allows for database locking and selecting in one trip to the server.
'           CAUTION: If the Begin or the Select returns Busy then will restart with
'                    the Begin.  Use Begin Immediate to prevent this or set ProcessMods
'                    to %gbfDoNotRetryIfBusy.
'      Q# = Will override the default delimiter(s).  # is one or two numbers
'           separated by a period.  The first is the ascii value character
'           that will delimit fields while the second is the ascii value
'           character that will delimit records.  Both are optional.
'                 ModChar         Field               Record
'                  Q9.13      Horizontal Tab       Carriage Return
'                  Q.30       Backspace(default)   Record Separator
'                  Q7             Bell             Vertical Tab(default)
'      R =  Release all named locks owned by this connection after doing
'           the SelAry.
'      D$ = Decrypt.
'      U$ = Uncompress.
'      N$ = Return NULL fields as $NUL.  CAUTION: You can not distinguish
'           between a true NULL field and one that contains a single
'           $NUL char.
'      t$ = If field is DateTime then do not return time.
'      z$ = If field is DateTime then do not return time if zero.
'      d$ = If field is DateTime then do not return date.
'      y$ = If field is DateTime then return empty if time is zero.
'   Note: The above $ is a comma delimited list of column numbers. Beware
'         that this columns apply to ALL rows.  So if, for example, some
'         times a column is compressed and sometimes not then do not use
'         slSelStr.  Build your own string from the selected rows.
'   Example: slSelStr "Select F1, F2 from T1", "D1,2N2" would
'            decrypt columns 1 and 2 and also return $NUL if column 2
'            is Null.
'   Note: This routine can be very useful when you have a small amount of
'         returning data, for testing the occurrence of a condition, or
'         for Pragma returns.  Lets say you wanted to know if there were
'         any rows that had an 'X' in column F1 of table T1:
'           if len(slSelStr(Select 1 from T1 where F1='X' Limit 1) then
'              ' if the above is true then you have at least 1 with 'X'
'           end if
'         This will display the curren page size return from a Pragma:
'           ? slSelStr("Pragma page_size")

   Local llA As Long
   Local llVT As Long
   Local lcA As Currency
   Local lsBS As String
   Dim lsaRows(0) As String

   ' Determine the delimiters
   llA = InStr(rsModChars, "Q")
   If llA Then
      lcA = Val(Mid$(rsModChars, llA + 1))
      lsBS = Format$(Fix(lcA))
      llVT = Val(Mid$(Format$(Frac(lcA)), 2))
   End If
   If IsFalse Val(lsBS) Then lsBS = "8"
   If IsFalse llVT Then llVT = 11

   ' Do they want column names
   If IsFalse InStr(rsModChars, "C") Then rsModChars &= "c"

   ' Build array and convert to string
   slSelAry rsStatement, lsaRows(), "Q" & lsBS & "_" & Remove$(rsModChars, "C")
   Function = Join$(lsaRows(), Chr$(llVT))

End Function

'========================<[ Set Process Mods ]>========================
Sub slSetProcessMods Alias "slSetProcessMods" (rsModChars As String)Export
'   Sets the process mod flags, modes, and/or values as controlled by
'   the passed ModChars.  Some of the mods are global while some are
'   for the open database.    No error can occur.
'   ModChars:
'      R  = Retry if busy.  This is the default.  If unable to obtain a
'           database or named locked then message will display asking if
'           want to retry. A database must be open.  This setting is
'           pushed/popped along with the database handle.
'      r  = Do not retry if busy. If unable to obtain a database or named
'           lock then no message will display and either an error will be
'           returned or raised.  A database must be open. This setting is
'           pushed/popped along with the database handle.
'      Em = Return errors.  This sets the global return errors flag on.
'           If compiled with the %ReturnAllErrors conditional compile set
'           to %True then this is the default upon start up.
'           m is the optional message display modifier and can be:
'              0 = No message is displayed.  This is the default.
'              1 = Display a warning message with OK button.  Error is
'                  returned when OK pressed.
'              2 = Display a question message with OK and Cancel buttons.
'                  If they press OK, error is returned.  If they press
'                  Cancel, will exit process.
'      e  = Do not return errors, display message and exit process. This
'           sets the global return errors flag off. If compiled with the
'           %ReturnAllErrors conditional compile set to %False then this
'           is the default upon start up.
'      Tn = Set SQLite Busy Timeout.  n = Number of milliseconds. Default
'           is 10000 or 10 seconds to wait for a database lock.  This is
'           originally set in slOpen.  If running in remote mode then the
'           TCP SEND/RECV wait as set in slConnect should be at least 3
'           times as long as this wait.  This setting is global.
'      Ln = Load either the local or remote processing lib and ruts. This
'           along with push/pop database allows you to access both a local
'           and remote database in the same running program.  WARNING: The
'           misuse of this may cause a GPF.
'              n = 0 will load SQLite3.DLL for Local Access Mode
'              n = 1 will load SQLiteningCliend.DLL. for Remote Access Mode
'      Kx = Set the crypt key for subsequent encryption/decryption.  x is
'           the key string, it must immediately follow the K.  If there is
'           no value following the K then any existing crypt key will be
'           cleared, this is for security reasons.  This ModChar MUST be
'           used alone since the letters in the key may be equal to other
'           ModChars.  If you have not written your own custom encryption
'           then the passed key must be either 16, 24, or 32 bytes long.

   Local llA As Long
   Local lhRutAddr As Dword

   ' Init stuff
   Reset tlLastError

   ' Handle crypt key
   llA = InStr(rsModChars, "K")
   If llA Then
      irCallAuxRutsA 1, Mid$(rsModChars, llA + 1), ""
      Exit Sub
   End If

   ' Set 'em on or off
   If InStr(rsModChars, "R") Then tuDbFlags.Bits.DoNotRetryIfBusy = %False
   If InStr(rsModChars, "r") Then tuDbFlags.Bits.DoNotRetryIfBusy = %True
   llA = InStr(rsModChars, "E")
   If llA Then
      guFlags.ReturnAllErrors = %True
      llA = Val(Mid$(rsModChars, llA + 1))
      If llA = 1 Then guFlags.DisplayErrorMessage = %True
      If llA = 2 Then guFlags.DisplayErrorMessageQuestion = %True
   End If
   If InStr(rsModChars, "e") Then guFlags.ReturnAllErrors = %False

   ' Handle T
   llA = InStr(rsModChars, "T")
   If llA Then
      If irCheckStatemetForAttachAndOpenDatabase("") Then Exit Sub
      irGetRutAddress "sqlite3_busy_timeout", lhRutAddr
      Call Dword lhRutAddr Using UsingSetBusyTimeout(thDab, Val(Mid$(rsModChars, llA + 1)))
   End If

   ' Handle L
   llA = InStr(rsModChars, "L")
   If llA Then
      irLoadProcessingLibAndRuts Val(Mid$(rsModChars, llA + 1))
   End If

End Sub

'======================<[ Set Rel Named Locks ]>=======================
Function slSetRelNamedLocks Alias "slSetRelNamedLocks" (ByRef rsLockNames As String, _
                                                        Optional ByVal rsModChars As String, _
                                                        Optional ByVal rsSelStatement As String, _
                                                        Optional ByVal rlSelSetNumber As Long, _
                                                        Optional ByVal rsSelModChars As String) Export As Long
'   Will set or release named lock(s).  This can be used to
'   implement application locks or to simulate record locks.  Names
'   are locked on a server-wide basis.  If a name has been locked by
'   one client, the server blocks any request by another client for
'   a lock with the same name.  This allows clients that agree on a
'   given lock name to use the name to perform cooperative named
'   locking.  But be aware that it also allows a client that is not
'   among the set of cooperating clients to lock a name and thus
'   prevent any of the cooperating clients from locking that name.
'   One way to reduce the likelihood of this is to use lock names
'   that are database-specific or application-specific.  Named locks
'   are only used in remote mode, ignored when running in local mode.
'   Will also optionally do a Sel command but only if the lock request
'   was successful.
'   LockNames:
'      This is a vertical tab ($VT) delimited list of names to set or release.
'      Be sure these names uniquely identify the lock you want to set.  If, for
'      example, you know the RowID of a record you want to lock you would need
'      to include some type of table ID within the name. The first character of
'      each name must be one of the following:
'         + (plus)  = Set lock
'         - (minus) = Release lock.
'      All named locks owned by a connection are automatically released when
'      the connection is no longer active.
'   ModChars:
'      Tn = Where n is the number of milliseconds to wait for a named
'           lock to be set before returning as unsuccessful.  If omitted
'           or is zero then will return immediately if a named lock can
'           not be set because it is owned by another connection.
'      R =  Release all named locks owned by this connection before setting
'           or releasing any named locks in LockNames.
'   SelStatement:
'      If the SelStatement is not empty and locking was successful then
'      will do the Sel command.  The Statement should be Select or Pragma
'      or any other that returns rows (Insert, Update, and Delete do not
'      return rows).
'   SelSetNumber:
'      SelSetNumber can be omitted or be any value from 0 to 32767. If omitted
'      then will use zero.  If SelStatement is empty then this is ignored.
'      SelSetNumber is used as an array index, no gaps is best, which will result
'      in a smaller array.  You can have as many unique sets open/active at same
'      time as your memory will allow.
'   SelModChars:
'      This value is ignored it SelStatement is empty.
'      C  = Will first do a slCloseSet(rlSetNumber). This will prevent
'           error 14 (%SQLitening_InvalidSetNumber) but should be used
'           with caution. Omiting set number or passing set number of
'           zero will do the same thing.
'      D  = Allow duplicate column names. Not recommended if using slFN or slFNX.
'           because you will always get the first value returned.  SQLite does
'           not normally return qualified column names.  SQLite will return C1
'           twice if you Select T1.C1, T2.C1. So the solution is to alias one of
'           them with the As clause as follows Select T1.C1, T2.C1 as C1Again.
'           There is a Pragma called "full_column_names" which forces SQLite to
'           return qualified names, but does not seem to work if you Select *.
'           Read up on it and use if you like.  I like using an alias because it
'           is less code and more clear.
'      Em = Return errors. This will override the global return errors flag.
'           m is the optional message display modifier and can be:
'              0 = No message is displayed.  This is the default.
'              1 = Display a warning message with OK button.  Error is
'                  returned when OK pressed.
'              2 = Display a question message with OK and Cancel buttons.
'                  If they press OK, error is returned.  If they press
'                  Cancel, will exit process.
'      e  = Do not return errors, display message and exit process. This
'           will override the global return errors flag.
'      Bn = Do a Begin Transaction before doing the Sel command. The type
'           of Begin is controlled by the value of n as follows:
'              0 = Deferred. This is the default if n is omitted.
'              1 = Immediate.
'              2 = Exclusive.
'           This allows for database locking and selecting in one trip to the server.
'           CAUTION: If the Begin or the Select returns Busy then will restart with
'                    the Begin.  Use Begin Immediate to prevent this or set ProcessMods
'                    to %gbfDoNotRetryIfBusy.
'      R =  Release all named locks owned by this connection after doing
'           the Sel.
'   ReturnCode:
'      Zero if locking was successful. If locking failed then depending on
'      ModChars and the global return errors flag, will either display error
'      and exit or will return %SQLitening_LockTimeout.  If locking failed then
'      no locks are set nor are any released and no Sel is run. If locking was
'      successful and there is an optional SelStatement then will return zero if
'      processed OK. Else, depending on ModChars and the global return errors flag,
'      will either display error and exit or will return the error number.
'   Examples:  (No error checking shown)
'      slSetRelNamedLocks("+Cust1395" & $VT & +"Cust40928")  --  Will set two named locks.
'      slSetRelNamedLocks("+Cust1395" & $VT & "-Cust40928")  --  Will set one named locks and release another.
'      slSetRelNamedLocks("-Cust1395")  --  Will release one named locks.
'      slSetRelNamedLocks("+Cust1395", "T15000R", "Select * from Cust where Num=1395")
'          Will first release all locks for this connection then set one named lock,
'          waiting up to 15 seconds. If lock was set OK then will select the customer.
'      slSetRelNamedLocks("", "R")   --  Will release all locks for this connection.
'      slSetRelNamedLocks("")   --  Will do nothing.

   Local lhRutAddr As Dword
   Local lsColumnNames As String

   ' Must handle differently if running remote
   If tuFlags.AreRunningRemote Then

      ' Do pre select stuff if there is a slSel commnad
      If Len(rsSelStatement) Then
         ' Close set if requested
         If InStr(rsSelModChars, "C") Or IsFalse rlSelSetNumber Then slCloseSet(rlSelSetNumber)
         ' Audit it
         If IsFalse irPreSelect(rsSelStatement, rlSelSetNumber) Then ErrorRut
      End If

      ' Call remote rut
      irGetRutAddress "SQLiteSetRelNamedLocks", lhRutAddr
      Do
         Call Dword lhRutAddr Using UsingSetRelNamedLocks(thDab, rsLockNames, rsModChars, rsSelStatement, rlSelSetNumber, rsSelModChars, lsColumnNames) To tlLastError
         If IsFalse tlLastError Or tlLastError = %SQLITE3_BUSY Then Exit Do
         If tlLastError <> %SQLitening_LockTimeout Or IsTrue tuDbFlags.Bits.DoNotRetryIfBusy Then ErrorRut
         If MsgBox(Left$("Named Locks " & rsLockNames & ", " & rsSelStatement, 255) & String$(2, $Cr) & _
                     "The above request was unsuccessful because the named lock(s) is currently locked.  Want to try again?", %MB_ICONQUESTION + %MB_YESNO, $Title) = %IDNo Then ErrorRut

      Loop

      ' Do post processing for the Sel stuff
      If Len(rsSelStatement) Then

         ' Check if had error
         If tlLastError And IsFalse irCheckIfBusy("Named Locks " & rsSelStatement) Then ErrorRut
         Do While tlLastError
            Call Dword thCallSel Using UsingSel(thDab, rsSelStatement, rlSelSetNumber, rsSelModChars, lsColumnNames) To tlLastError
            If tlLastError And IsFalse irCheckIfBusy("Named Locks " & rsLockNames & " " & rsSelStatement) Then GoTo ErrorRut
         Loop While tlLastError = %SQLITE3_BUSY

         ' Mark the set as active
         thaSets(rlSelSetNumber) = 1

          ' Do post select stuff
         If Len(rsSelStatement) Then If IsFalse irPostSelect(lsColumnNames, rlSelSetNumber, rsSelModChars) Then ErrorRut

      End If

   Else

      ' Call slSel if they passed a statement
      If Len(rsSelStatement) Then Function = slSel(rsSelStatement, rlSelSetNumber, rsSelModChars)

   End If

   ' Exit OK
   Exit Function

   ErrorRut:
   irHandleError "Named Locks " & rsLockNames & ",  " & rsSelStatement, rsModChars & rsSelModChars
   Function = tlLastError

End Function

'============================<[ Lib Main ]>============================
%DLL_PROCESS_ATTACH                          = 1
%DLL_THREAD_ATTACH                           = 2
%DLL_THREAD_DETACH                           = 3
%DLL_PROCESS_DETACH                          = 0
Function LibMain(ByVal InstDLL    As Long, _
                 ByVal Reason   As Long, _
                 ByVal Reserved As Long)As Long
' InstDLL is the DLL's instance handle.  This handle is used by the calling
'    application to identify the DLL being called.
' Reason specifies a flag indicating why the DLL entry-point is being called.
'    It can be one of the following values:
'    %DLL_PROCESS_ATTACH=1: Indicates that the DLL is being loaded by another process
'    (a DLL or EXE is loading the DLL).  DLLs can use this opportunity to initialize
'    any instance or global data, such as arrays.
'    %DLL_PROCESS_DETACH=0: Indicates that the DLL is being unloaded or detached from
'    the calling application.  DLLs can take this opportunity to clean up all
'    resources for all threads attached and known to the DLL.
'    %DLL_THREAD_ATTACH=2: Indicates that the DLL is being loaded by a new thread in
'    the calling application.  DLLs can use this opportunity to initialize any thread
'    local storage (TLS).
'    %DLL_THREAD_DETACH=3: Indicates that the thread is exiting cleanly.  If the DLL
'    has allocated any thread local storage, it should be released.
' Reserved specifies further aspects of the DLL initialization and cleanup.  If
'    Reason is %DLL_PROCESS_ATTACH, Reserved is NULL (zero) for dynamic loads
'    and non-NULL for static loads.  If Reason is %DLL_PROCESS_DETACH, Reserved
'    is NULL if LibMain has been called by using the FreeLibrary API call and
'    non-NULL if LibMain has been called during process termination.
' Return: If LibMain is called with %DLL_PROCESS_ATTACH, your LibMain function should
'    return a zero (0) if any part of your initialization process fails or a one (1)
'    if no errors were encountered.  If a zero is returned, Windows will abort and
'    unload the DLL from memory. When LibMain is called with any other value than
'    %DLL_PROCESS_ATTACH, the return value is ignored.

   ' Start and stop stuff
   Select Case Long Reason
   Case %DLL_PROCESS_ATTACH

      ' If the Return All Errors contitional compile is on the
      ' set the global flag
      #If %ReturnAllErrors
         tuFlags.ReturnAllErrors = %True
      #EndIf

   Case %DLL_THREAD_DETACH, %DLL_PROCESS_DETACH

      ' Close database if one is open
      If thDab Then slClose

   End Select

   ' Exit OK
   Function = 1

End Function
